Vecinos más cercanos aproximados en TSNE

Machine LearningMachine LearningBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderemos a usar vecinos más cercanos aproximados en TSNE utilizando la biblioteca scikit-learn de Python.

Consejos sobre la VM

Una vez finalizada la inicialización de la VM, haga clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tenga que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de las operaciones no se puede automatizar debido a las limitaciones de Jupyter Notebook.

Si tiene problemas durante el aprendizaje, no dude en preguntar a Labby. Deje sus comentarios después de la sesión y lo resolveremos rápidamente para usted.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL sklearn(("Sklearn")) -.-> sklearn/DataPreprocessingandFeatureEngineeringGroup(["Data Preprocessing and Feature Engineering"]) sklearn(("Sklearn")) -.-> sklearn/AdvancedDataAnalysisandDimensionalityReductionGroup(["Advanced Data Analysis and Dimensionality Reduction"]) sklearn(("Sklearn")) -.-> sklearn/UtilitiesandDatasetsGroup(["Utilities and Datasets"]) ml(("Machine Learning")) -.-> ml/FrameworkandSoftwareGroup(["Framework and Software"]) sklearn(("Sklearn")) -.-> sklearn/CoreModelsandAlgorithmsGroup(["Core Models and Algorithms"]) sklearn/CoreModelsandAlgorithmsGroup -.-> sklearn/neighbors("Nearest Neighbors") sklearn/DataPreprocessingandFeatureEngineeringGroup -.-> sklearn/pipeline("Pipeline") sklearn/AdvancedDataAnalysisandDimensionalityReductionGroup -.-> sklearn/manifold("Manifold Learning") sklearn/UtilitiesandDatasetsGroup -.-> sklearn/base("Base Classes and Utility Functions") sklearn/UtilitiesandDatasetsGroup -.-> sklearn/utils("Utilities") sklearn/UtilitiesandDatasetsGroup -.-> sklearn/datasets("Datasets") ml/FrameworkandSoftwareGroup -.-> ml/sklearn("scikit-learn") subgraph Lab Skills sklearn/neighbors -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} sklearn/pipeline -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} sklearn/manifold -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} sklearn/base -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} sklearn/utils -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} sklearn/datasets -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} ml/sklearn -.-> lab-49054{{"Vecinos más cercanos aproximados en TSNE"}} end

Instalar los paquetes necesarios

Necesitamos instalar los paquetes nmslib y pynndescent. Estos paquetes se pueden instalar utilizando el comando pip.

!pip install nmslib pynndescent

Importar las bibliotecas necesarias

Necesitamos importar las bibliotecas necesarias, incluyendo nmslib, pynndescent, sklearn, numpy, scipy y matplotlib.

import sys
import joblib
import numpy as np
import matplotlib.pyplot as plt
from scipy.sparse import csr_matrix
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.datasets import fetch_openml
from sklearn.utils import shuffle
from sklearn.manifold import TSNE
from sklearn.neighbors import KNeighborsTransformer
from sklearn.pipeline import make_pipeline
from pynndescent import PyNNDescentTransformer
import nmslib

Definir una clase wrapper para nmslib

Definimos una clase wrapper para nmslib para implementar la API de scikit-learn para nmslib, así como una función de carga. La clase NMSlibTransformer toma n_neighbors, metric, method y n_jobs como parámetros. El método fit() inicializa nmslib y agrega los puntos de datos a él. El método transform() encuentra los vecinos más cercanos y devuelve una matriz dispersa.

class NMSlibTransformer(TransformerMixin, BaseEstimator):
    """Wrapper para usar nmslib como KNeighborsTransformer de sklearn"""

    def __init__(self, n_neighbors=5, metric="euclidean", method="sw-graph", n_jobs=-1):
        self.n_neighbors = n_neighbors
        self.method = method
        self.metric = metric
        self.n_jobs = n_jobs

    def fit(self, X):
        self.n_samples_fit_ = X.shape[0]

        ## Ver más métricas en el manual
        ## https://github.com/nmslib/nmslib/tree/master/manual
        space = {
            "euclidean": "l2",
            "cosine": "cosinesimil",
            "l1": "l1",
            "l2": "l2",
        }[self.metric]

        self.nmslib_ = nmslib.init(method=self.method, space=space)
        self.nmslib_.addDataPointBatch(X.copy())
        self.nmslib_.createIndex()
        return self

    def transform(self, X):
        n_samples_transform = X.shape[0]

        ## Por razones de compatibilidad, dado que cada muestra se considera como su propio
        ## vecino, se calculará un vecino adicional.
        n_neighbors = self.n_neighbors + 1

        if self.n_jobs < 0:
            ## Mismo manejo que se hace en joblib para valores negativos de n_jobs:
            ## en particular, `n_jobs == -1` significa "tantos hilos como CPUs".
            num_threads = joblib.cpu_count() + self.n_jobs + 1
        else:
            num_threads = self.n_jobs

        results = self.nmslib_.knnQueryBatch(
            X.copy(), k=n_neighbors, num_threads=num_threads
        )
        indices, distances = zip(*results)
        indices, distances = np.vstack(indices), np.vstack(distances)

        indptr = np.arange(0, n_samples_transform * n_neighbors + 1, n_neighbors)
        kneighbors_graph = csr_matrix(
            (distances.ravel(), indices.ravel(), indptr),
            shape=(n_samples_transform, self.n_samples_fit_),
        )

        return kneighbors_graph

Definir una función para cargar el conjunto de datos MNIST

Definimos una función load_mnist() para cargar el conjunto de datos MNIST, mezclar los datos y devolver solo un número específico de muestras.

def load_mnist(n_samples):
    """Cargar MNIST, mezclar los datos y devolver solo n_samples."""
    mnist = fetch_openml("mnist_784", as_frame=False, parser="pandas")
    X, y = shuffle(mnist.data, mnist.target, random_state=2)
    return X[:n_samples] / 255, y[:n_samples]

Realizar pruebas de rendimiento de diferentes transformadores de vecinos más cercanos

Realizamos pruebas de rendimiento de los diferentes transformadores exactos/aproximados de vecinos más cercanos. Definimos los conjuntos de datos, los transformadores y los parámetros, incluyendo n_iter, perplexity, metric y n_neighbors. Medimos el tiempo que tarda en ajustar y transformar cada transformador en cada conjunto de datos. Imprimimos el tiempo que tardó en ajustar y transformar cada transformador.

datasets = [
    ("MNIST_10000", load_mnist(n_samples=10_000)),
    ("MNIST_20000", load_mnist(n_samples=20_000)),
]

n_iter = 500
perplexity = 30
metric = "euclidean"
n_neighbors = int(3.0 * perplexity + 1) + 1

tsne_params = dict(
    init="random",  ## pca no admitido para matrices dispersas
    perplexity=perplexity,
    method="barnes_hut",
    random_state=42,
    n_iter=n_iter,
    learning_rate="auto",
)

transformers = [
    (
        "KNeighborsTransformer",
        KNeighborsTransformer(n_neighbors=n_neighbors, mode="distance", metric=metric),
    ),
    (
        "NMSlibTransformer",
        NMSlibTransformer(n_neighbors=n_neighbors, metric=metric),
    ),
    (
        "PyNNDescentTransformer",
        PyNNDescentTransformer(
            n_neighbors=n_neighbors, metric=metric, parallel_batch_queries=True
        ),
    ),
]

for dataset_name, (X, y) in datasets:
    msg = f"Realizando pruebas de rendimiento en {dataset_name}:"
    print(f"\n{msg}\n" + str("-" * len(msg)))

    for transformer_name, transformer in transformers:
        longest = np.max([len(name) for name, model in transformers])
        start = time.time()
        transformer.fit(X)
        fit_duration = time.time() - start
        print(f"{transformer_name:<{longest}} {fit_duration:.3f} sec (ajustar)")
        start = time.time()
        Xt = transformer.transform(X)
        transform_duration = time.time() - start
        print(f"{transformer_name:<{longest}} {transform_duration:.3f} sec (transformar)")
        if transformer_name == "PyNNDescentTransformer":
            start = time.time()
            Xt = transformer.transform(X)
            transform_duration = time.time() - start
            print(
                f"{transformer_name:<{longest}} {transform_duration:.3f} sec"
                " (transformar)"
            )

Visualizar la incrustación TSNE

Visualizamos las incrustaciones TSNE utilizando diferentes transformadores de vecinos más cercanos. Definimos transformers como una lista que contiene tres tuberías: TSNE con NearestNeighbors interno, TSNE con KNeighborsTransformer y TSNE con NMSlibTransformer. Iteramos sobre los conjuntos de datos y los transformadores y graficamos las incrustaciones TSNE, que deberían ser similares entre los métodos. Mostramos la gráfica al final.

transformers = [
    ("TSNE with internal NearestNeighbors", TSNE(metric=metric, **tsne_params)),
    (
        "TSNE with KNeighborsTransformer",
        make_pipeline(
            KNeighborsTransformer(
                n_neighbors=n_neighbors, mode="distance", metric=metric
            ),
            TSNE(metric="precomputed", **tsne_params),
        ),
    ),
    (
        "TSNE with NMSlibTransformer",
        make_pipeline(
            NMSlibTransformer(n_neighbors=n_neighbors, metric=metric),
            TSNE(metric="precomputed", **tsne_params),
        ),
    ),
]

nrows = len(datasets)
ncols = np.sum([1 for name, model in transformers if "TSNE" in name])
fig, axes = plt.subplots(
    nrows=nrows, ncols=ncols, squeeze=False, figsize=(5 * ncols, 4 * nrows)
)
axes = axes.ravel()
i_ax = 0

for dataset_name, (X, y) in datasets:
    msg = f"Realizando pruebas de rendimiento en {dataset_name}:"
    print(f"\n{msg}\n" + str("-" * len(msg)))

    for transformer_name, transformer in transformers:
        longest = np.max([len(name) for name, model in transformers])
        start = time.time()
        Xt = transformer.fit_transform(X)
        transform_duration = time.time() - start
        print(
            f"{transformer_name:<{longest}} {transform_duration:.3f} sec"
            " (ajustar_transformar)"
        )

        ## graficar la incrustación TSNE que debería ser muy similar entre los métodos
        axes[i_ax].set_title(transformer_name + "\nsobre " + dataset_name)
        axes[i_ax].scatter(
            Xt[:, 0],
            Xt[:, 1],
            c=y.astype(np.int32),
            alpha=0.2,
            cmap=plt.cm.viridis,
        )
        axes[i_ax].xaxis.set_major_formatter(NullFormatter())
        axes[i_ax].yaxis.set_major_formatter(NullFormatter())
        axes[i_ax].axis("tight")
        i_ax += 1

fig.tight_layout()
plt.show()

Resumen

En este laboratorio, aprendimos cómo utilizar vecinos más cercanos aproximados en TSNE utilizando la biblioteca scikit-learn de Python. Importamos las bibliotecas necesarias, definimos una clase wrapper para nmslib, definimos una función para cargar el conjunto de datos MNIST, realizamos pruebas de rendimiento de diferentes transformadores de vecinos más cercanos y visualizamos las incrustaciones TSNE. Aprendimos que el estimador predeterminado TSNE con su implementación interna de NearestNeighbors es aproximadamente equivalente a la tubería con TSNE y KNeighborsTransformer en términos de rendimiento. También aprendimos que el aproximado NMSlibTransformer ya es ligeramente más rápido que la búsqueda exacta en el conjunto de datos más pequeño, pero se espera que esta diferencia de velocidad sea más significativa en conjuntos de datos con un mayor número de muestras.