Visualiza incorporaciones con t-SNE

Ver en ai.google.dev Ejecutar en Google Colab Ver código fuente en GitHub

Descripción general

En este instructivo, se muestra cómo visualizar y realizar el agrupamiento en clústeres con las incorporaciones de la API de Gemini. Visualizarás un subconjunto del conjunto de datos de 20 grupos de noticias mediante t-SNE y agruparás ese subconjunto con el algoritmo KMeans.

Si quieres obtener más información para comenzar a usar las incorporaciones generadas a partir de la API de Gemini, consulta la guía de inicio rápido de Python.

Requisitos previos

Puedes ejecutar esta guía de inicio rápido en Google Colab.

Para completar esta guía de inicio rápido en tu propio entorno de desarrollo, asegúrate de que tu entorno cumpla con los siguientes requisitos:

  • Python 3.9 y versiones posteriores
  • Una instalación de jupyter para ejecutar el notebook

Configuración

Primero, descarga e instala la biblioteca de Python de la API de Gemini.

pip install -U -q google.generativeai
import re
import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import google.generativeai as genai
import google.ai.generativelanguage as glm

# Used to securely store your API key
from google.colab import userdata

from sklearn.datasets import fetch_20newsgroups
from sklearn.manifold import TSNE
from sklearn.cluster import KMeans
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

Obtén una clave de API

Para poder usar la API de Gemini, primero debes obtener una clave de API. Si aún no tienes una, crea una con un clic en Google AI Studio.

Obtén una clave de API.

En Colab, agrega la clave al administrador de Secrets en la "automated" del panel izquierdo. Asígnale el nombre API_KEY.

Una vez que tengas la clave de API, pásala al SDK. Puedes hacerlo de dos maneras:

  • Coloca la clave en la variable de entorno GOOGLE_API_KEY (el SDK la recogerá automáticamente desde allí).
  • Pasa la llave a genai.configure(api_key=...)
# Or use `os.getenv('API_KEY')` to fetch an environment variable.
API_KEY=userdata.get('API_KEY')

genai.configure(api_key=API_KEY)
for m in genai.list_models():
  if 'embedContent' in m.supported_generation_methods:
    print(m.name)
models/embedding-001
models/embedding-001

Conjunto de datos

El conjunto de datos de texto de 20 grupos de noticias contiene 18,000 publicaciones de grupos de noticias sobre 20 temas divididos en conjuntos de entrenamiento y pruebas. La división entre los conjuntos de datos de entrenamiento y de prueba se basa en los mensajes publicados antes y después de una fecha específica. Para este instructivo, usarás el subconjunto de entrenamiento.

newsgroups_train = fetch_20newsgroups(subset='train')

# View list of class names for dataset
newsgroups_train.target_names
['alt.atheism',
 'comp.graphics',
 'comp.os.ms-windows.misc',
 'comp.sys.ibm.pc.hardware',
 'comp.sys.mac.hardware',
 'comp.windows.x',
 'misc.forsale',
 'rec.autos',
 'rec.motorcycles',
 'rec.sport.baseball',
 'rec.sport.hockey',
 'sci.crypt',
 'sci.electronics',
 'sci.med',
 'sci.space',
 'soc.religion.christian',
 'talk.politics.guns',
 'talk.politics.mideast',
 'talk.politics.misc',
 'talk.religion.misc']

Este es el primer ejemplo del conjunto de entrenamiento.

idx = newsgroups_train.data[0].index('Lines')
print(newsgroups_train.data[0][idx:])
Lines: 15

 I was wondering if anyone out there could enlighten me on this car I saw
the other day. It was a 2-door sports car, looked to be from the late 60s/
early 70s. It was called a Bricklin. The doors were really small. In addition,
the front bumper was separate from the rest of the body. This is 
all I know. If anyone can tellme a model name, engine specs, years
of production, where this car is made, history, or whatever info you
have on this funky looking car, please e-mail.

Thanks,

- IL
   ---- brought to you by your neighborhood Lerxst ----
# Apply functions to remove names, emails, and extraneous words from data points in newsgroups.data
newsgroups_train.data = [re.sub(r'[\w\.-]+@[\w\.-]+', '', d) for d in newsgroups_train.data] # Remove email
newsgroups_train.data = [re.sub(r"\([^()]*\)", "", d) for d in newsgroups_train.data] # Remove names
newsgroups_train.data = [d.replace("From: ", "") for d in newsgroups_train.data] # Remove "From: "
newsgroups_train.data = [d.replace("\nSubject: ", "") for d in newsgroups_train.data] # Remove "\nSubject: "
# Put training points into a dataframe
df_train = pd.DataFrame(newsgroups_train.data, columns=['Text'])
df_train['Label'] = newsgroups_train.target
# Match label to target name index
df_train['Class Name'] = df_train['Label'].map(newsgroups_train.target_names.__getitem__)
# Retain text samples that can be used in the gecko model.
df_train = df_train[df_train['Text'].str.len() < 10000]

df_train

A continuación, tomarás una muestra de algunos de los datos. Para ello, tomarás 100 datos en el conjunto de datos de entrenamiento y quitarás algunas de las categorías que ejecutarás en este instructivo. Elige las categorías de ciencia que quieres comparar.

# Take a sample of each label category from df_train
SAMPLE_SIZE = 150
df_train = (df_train.groupby('Label', as_index = False)
                    .apply(lambda x: x.sample(SAMPLE_SIZE))
                    .reset_index(drop=True))

# Choose categories about science
df_train = df_train[df_train['Class Name'].str.contains('sci')]

# Reset the index
df_train = df_train.reset_index()
df_train
df_train['Class Name'].value_counts()
sci.crypt          150
sci.electronics    150
sci.med            150
sci.space          150
Name: Class Name, dtype: int64

Crea las incorporaciones

En esta sección, aprenderás a generar incorporaciones para los diferentes textos en el marco de datos con las incorporaciones de la API de Gemini.

Cambios de API a incorporaciones con el modelo embedding-001

Para el nuevo modelo de incorporaciones, embedding-001, hay un nuevo parámetro de tipo de tarea y el título opcional (solo válido con task_type=RETRIEVAL_DOCUMENT).

Estos parámetros nuevos se aplican solo a los modelos de incorporaciones más recientes.Los tipos de tareas son los siguientes:

Tipo de tarea Descripción
RETRIEVAL_QUERY Especifica que el texto dado es una consulta en un parámetro de configuración de búsqueda/recuperación.
RETRIEVAL_DOCUMENT Especifica que el texto dado de un documento en un parámetro de configuración de búsqueda y recuperación.
SEMANTIC_SIMILARITY Especifica que el texto dado se usará para la similitud textual semántica (STS).
CLASIFICACIÓN Especifica que las incorporaciones se usarán para la clasificación.
CLÚSTERES Especifica que las incorporaciones se usarán para el agrupamiento en clústeres.
from tqdm.auto import tqdm
tqdm.pandas()

from google.api_core import retry

def make_embed_text_fn(model):

  @retry.Retry(timeout=300.0)
  def embed_fn(text: str) -> list[float]:
    # Set the task_type to CLUSTERING.
    embedding = genai.embed_content(model=model,
                                    content=text,
                                    task_type="clustering")
    return embedding["embedding"]

  return embed_fn

def create_embeddings(df):
  model = 'models/embedding-001'
  df['Embeddings'] = df['Text'].progress_apply(make_embed_text_fn(model))
  return df

df_train = create_embeddings(df_train)
0%|          | 0/600 [00:00<?, ?it/s]

Reducción de la dimensionalidad

El largo del vector de incorporación del documento es 768. Para visualizar cómo se agrupan los documentos incorporados, deberás aplicar una reducción de dimensiones, ya que solo puedes visualizar las incorporaciones en un espacio 2D o 3D. Los documentos contextualmente similares deben estar más cerca en el espacio, a diferencia de los documentos que no son tan similares.

len(df_train['Embeddings'][0])
768
# Convert df_train['Embeddings'] Pandas series to a np.array of float32
X = np.array(df_train['Embeddings'].to_list(), dtype=np.float32)
X.shape
(600, 768)

Aplicarás el enfoque de Incorporación de Vecinos Estocásticos Distribuidos (t-SNE) para realizar la reducción de la dimensionalidad. Esta técnica reduce la cantidad de dimensiones y, al mismo tiempo, mantiene los clústeres (los puntos que están juntos permanecen juntos). Para los datos originales, el modelo intenta construir una distribución sobre la cual otros datos son “vecinos” (p.ej., tienen significado similar). Luego, optimiza una función objetiva para mantener una distribución similar en la visualización.

tsne = TSNE(random_state=0, n_iter=1000)
tsne_results = tsne.fit_transform(X)
df_tsne = pd.DataFrame(tsne_results, columns=['TSNE1', 'TSNE2'])
df_tsne['Class Name'] = df_train['Class Name'] # Add labels column from df_train to df_tsne
df_tsne
fig, ax = plt.subplots(figsize=(8,6)) # Set figsize
sns.set_style('darkgrid', {"grid.color": ".6", "grid.linestyle": ":"})
sns.scatterplot(data=df_tsne, x='TSNE1', y='TSNE2', hue='Class Name', palette='hls')
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
plt.title('Scatter plot of news using t-SNE');
plt.xlabel('TSNE1');
plt.ylabel('TSNE2');
plt.axis('equal')
(-46.191162300109866,
 53.521015357971194,
 -39.96646995544434,
 37.282975387573245)

png

Compara los resultados con KMeans

El agrupamiento en clústeres de KMeans es un algoritmo popular de agrupamiento en clústeres y se usa con frecuencia para el aprendizaje no supervisado. Determina de manera iterativa los mejores puntos centrales k y asigna cada ejemplo al centroide más cercano. Ingresa las incorporaciones directamente en el algoritmo KMeans para comparar la visualización de las incorporaciones con el rendimiento de un algoritmo de aprendizaje automático.

# Apply KMeans
kmeans_model = KMeans(n_clusters=4, random_state=1, n_init='auto').fit(X)
labels = kmeans_model.fit_predict(X)
df_tsne['Cluster'] = labels
df_tsne
fig, ax = plt.subplots(figsize=(8,6)) # Set figsize
sns.set_style('darkgrid', {"grid.color": ".6", "grid.linestyle": ":"})
sns.scatterplot(data=df_tsne, x='TSNE1', y='TSNE2', hue='Cluster', palette='magma')
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
plt.title('Scatter plot of news using KMeans Clustering');
plt.xlabel('TSNE1');
plt.ylabel('TSNE2');
plt.axis('equal')
(-46.191162300109866,
 53.521015357971194,
 -39.96646995544434,
 37.282975387573245)

png

def get_majority_cluster_per_group(df_tsne_cluster, class_names):
  class_clusters = dict()
  for c in class_names:
    # Get rows of dataframe that are equal to c
    rows = df_tsne_cluster.loc[df_tsne_cluster['Class Name'] == c]
    # Get majority value in Cluster column of the rows selected
    cluster = rows.Cluster.mode().values[0]
    # Populate mapping dictionary
    class_clusters[c] = cluster
  return class_clusters
classes = df_tsne['Class Name'].unique()
class_clusters = get_majority_cluster_per_group(df_tsne, classes)
class_clusters
{'sci.crypt': 1, 'sci.electronics': 3, 'sci.med': 2, 'sci.space': 0}

Obtener la mayoría de los clústeres por grupo y ver cuántos de los miembros reales de ese grupo están en ese clúster

# Convert the Cluster column to use the class name
class_by_id = {v: k for k, v in class_clusters.items()}
df_tsne['Predicted'] = df_tsne['Cluster'].map(class_by_id.__getitem__)

# Filter to the correctly matched rows
correct = df_tsne[df_tsne['Class Name'] == df_tsne['Predicted']]

# Summarise, as a percentage
acc = correct['Class Name'].value_counts() / SAMPLE_SIZE
acc
sci.space          0.966667
sci.med            0.960000
sci.electronics    0.953333
sci.crypt          0.926667
Name: Class Name, dtype: float64
# Get predicted values by name
df_tsne['Predicted'] = ''
for idx, rows in df_tsne.iterrows():
  cluster = rows['Cluster']
  # Get key from mapping based on cluster value
  key = list(class_clusters.keys())[list(class_clusters.values()).index(cluster)]
  df_tsne.at[idx, 'Predicted'] = key

df_tsne

Para visualizar mejor el rendimiento de los KMeans aplicados a tus datos, puedes usar una matriz de confusión. La matriz de confusión te permite evaluar el rendimiento del modelo de clasificación más allá de la exactitud. Puedes ver en qué se clasifican los puntos mal clasificados. Necesitarás los valores reales y los valores predichos, que recopilaste en el marco de datos anterior.

cm = confusion_matrix(df_tsne['Class Name'].to_list(), df_tsne['Predicted'].to_list())
disp = ConfusionMatrixDisplay(confusion_matrix=cm,
                              display_labels=classes)
disp.plot(xticks_rotation='vertical')
plt.title('Confusion Matrix for Actual and Clustered Newsgroups');
plt.grid(False)

png

Próximos pasos

Ya creaste tu propia visualización de incorporaciones con agrupamiento en clústeres. Intenta usar tus propios datos textuales para visualizarlos como incorporaciones. Puedes reducir la dimensionalidad para completar el paso de visualización. Ten en cuenta que TSNE es bueno para agrupar entradas en clústeres, pero puede tardar más tiempo en converger o bloquearse en los mínimos locales. Si te encuentras con este problema, otra técnica que podrías considerar es el análisis de componentes principales (APC).

También hay otros algoritmos de agrupamiento en clústeres fuera de KMeans, como el agrupamiento en clústeres espacial basado en densidad (DBSCAN).

Para obtener información sobre cómo usar otros servicios en la API de Gemini, visita la guía de inicio rápido de Python. Para obtener más información sobre cómo usar las incorporaciones, consulta los ejemplos disponibles. Para aprender a crearlas desde cero, consulta el instructivo Incorporación de palabras de TensorFlow.