Análisis de conglomerados en documentos de texto utilizando k-medias(k-means)

Este es un ejemplo que muestra cómo se puede utilizar scikit-learn para agrupar documentos por temas utilizando un enfoque de bolsa de palabras. Este ejemplo utiliza una matriz scipy.sparse para almacenar las características en lugar de arreglos numpy estándar.

En este ejemplo se pueden utilizar dos métodos de extracción de características:

  • TfidfVectorizer utiliza un vocabulario en memoria (un diccionario de python) para mapear las palabras más frecuentes a los índices de características y, por tanto, calcular una matriz de frecuencia de aparición de palabras (dispersa). A continuación, las frecuencias de las palabras son reponderadas utilizando el vector de la frecuencia inversa del documento (Inverse Document Frequency, IDF) recogido por las características del corpus.

  • El HashingVectorizer hace un hash de las ocurrencias de las palabras en un espacio dimensional fijo, posiblemente con colisiones. Los vectores de conteo de palabras se normalizan para que cada uno tenga una norma l2 igual a uno (proyectada al círculo unitario euclidiano), lo que parece ser importante para que k-medias funcione en un espacio de alta dimensión.

    HashingVectorizer no proporciona ponderación IDF ya que es un modelo sin estado (el método de ajuste no hace nada). Cuando se necesita la ponderación IDF se puede añadir mediante un pipeline de su salida a una instancia TfidfTransformer.

Se demuestran dos algoritmos: k-medias ordinario y su primo más escalable k-medias de minilote.

Además, el análisis semántico latente también puede utilizarse para reducir la dimensionalidad y descubrir patrones latentes en los datos.

Puede observarse que k-medias (y k-medias de minilote) son muy sensibles al escalamiento de características y que, en este caso, la ponderación IDF ayuda a mejorar la calidad del análisis de conglomerados en gran medida en comparación con la «verdad fundamental» proporcionada por las asignaciones de etiquetas de clase del conjunto de datos de 20 grupos de noticias.

Esta mejora no es visible en el Coeficiente de Silueta, que es pequeño para ambos, ya que esta medida parece sufrir el fenómeno llamado «Concentración de la Medida» o «Maldición de la Dimensionalidad» para conjuntos de datos de alta dimensión como los datos de texto. Otras medidas, como la medida V y el índice de Rand ajustado, son puntuaciones de evaluación basadas en la teoría de la información, ya que sólo se basan en la asignación de conglomerados y no en las distancias, por lo que no se ven afectadas por la maldición de la dimensionalidad.

Nota: como k-medias está optimizando una función objetivo no convexa, es probable que termine en un óptimo local. Pueden ser necesarias varias ejecuciones con inicio aleatorio independiente para conseguir una buena convergencia.

Out:

Usage: plot_document_clustering.py [options]

Options:
  -h, --help            show this help message and exit
  --lsa=N_COMPONENTS    Preprocess documents with latent semantic analysis.
  --no-minibatch        Use ordinary k-means algorithm (in batch mode).
  --no-idf              Disable Inverse Document Frequency feature weighting.
  --use-hashing         Use a hashing feature vectorizer
  --n-features=N_FEATURES
                        Maximum number of features (dimensions) to extract
                        from text.
  --verbose             Print progress reports inside k-means algorithm.
Loading 20 newsgroups dataset for categories:
['alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space']
3387 documents
4 categories

Extracting features from the training dataset using a sparse vectorizer
done in 0.775435s
n_samples: 3387, n_features: 10000

Clustering sparse data with MiniBatchKMeans(batch_size=1000, init_size=1000, n_clusters=4, n_init=1,
                verbose=False)
done in 0.094s

Homogeneity: 0.219
Completeness: 0.338
V-measure: 0.266
Adjusted Rand-Index: 0.113
Silhouette Coefficient: 0.005

Top terms per cluster:
Cluster 0: cc ibm au buffalo monash com vnet software nicho university
Cluster 1: space nasa henry access digex toronto gov pat alaska shuttle
Cluster 2: com god university article don know graphics people posting like
Cluster 3: sgi keith livesey morality jon solntze wpd caltech objective moral

# Author: Peter Prettenhofer <peter.prettenhofer@gmail.com>
#         Lars Buitinck
# License: BSD 3 clause
from sklearn.datasets import fetch_20newsgroups
from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import Normalizer
from sklearn import metrics

from sklearn.cluster import KMeans, MiniBatchKMeans

import logging
from optparse import OptionParser
import sys
from time import time

import numpy as np


# Display progress logs on stdout
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(levelname)s %(message)s')

# parse commandline arguments
op = OptionParser()
op.add_option("--lsa",
              dest="n_components", type="int",
              help="Preprocess documents with latent semantic analysis.")
op.add_option("--no-minibatch",
              action="store_false", dest="minibatch", default=True,
              help="Use ordinary k-means algorithm (in batch mode).")
op.add_option("--no-idf",
              action="store_false", dest="use_idf", default=True,
              help="Disable Inverse Document Frequency feature weighting.")
op.add_option("--use-hashing",
              action="store_true", default=False,
              help="Use a hashing feature vectorizer")
op.add_option("--n-features", type=int, default=10000,
              help="Maximum number of features (dimensions)"
                   " to extract from text.")
op.add_option("--verbose",
              action="store_true", dest="verbose", default=False,
              help="Print progress reports inside k-means algorithm.")

print(__doc__)
op.print_help()


def is_interactive():
    return not hasattr(sys.modules['__main__'], '__file__')


# work-around for Jupyter notebook and IPython console
argv = [] if is_interactive() else sys.argv[1:]
(opts, args) = op.parse_args(argv)
if len(args) > 0:
    op.error("this script takes no arguments.")
    sys.exit(1)


# #############################################################################
# Load some categories from the training set
categories = [
    'alt.atheism',
    'talk.religion.misc',
    'comp.graphics',
    'sci.space',
]
# Uncomment the following to do the analysis on all the categories
# categories = None

print("Loading 20 newsgroups dataset for categories:")
print(categories)

dataset = fetch_20newsgroups(subset='all', categories=categories,
                             shuffle=True, random_state=42)

print("%d documents" % len(dataset.data))
print("%d categories" % len(dataset.target_names))
print()

labels = dataset.target
true_k = np.unique(labels).shape[0]

print("Extracting features from the training dataset "
      "using a sparse vectorizer")
t0 = time()
if opts.use_hashing:
    if opts.use_idf:
        # Perform an IDF normalization on the output of HashingVectorizer
        hasher = HashingVectorizer(n_features=opts.n_features,
                                   stop_words='english', alternate_sign=False,
                                   norm=None)
        vectorizer = make_pipeline(hasher, TfidfTransformer())
    else:
        vectorizer = HashingVectorizer(n_features=opts.n_features,
                                       stop_words='english',
                                       alternate_sign=False, norm='l2')
else:
    vectorizer = TfidfVectorizer(max_df=0.5, max_features=opts.n_features,
                                 min_df=2, stop_words='english',
                                 use_idf=opts.use_idf)
X = vectorizer.fit_transform(dataset.data)

print("done in %fs" % (time() - t0))
print("n_samples: %d, n_features: %d" % X.shape)
print()

if opts.n_components:
    print("Performing dimensionality reduction using LSA")
    t0 = time()
    # Vectorizer results are normalized, which makes KMeans behave as
    # spherical k-means for better results. Since LSA/SVD results are
    # not normalized, we have to redo the normalization.
    svd = TruncatedSVD(opts.n_components)
    normalizer = Normalizer(copy=False)
    lsa = make_pipeline(svd, normalizer)

    X = lsa.fit_transform(X)

    print("done in %fs" % (time() - t0))

    explained_variance = svd.explained_variance_ratio_.sum()
    print("Explained variance of the SVD step: {}%".format(
        int(explained_variance * 100)))

    print()


# #############################################################################
# Do the actual clustering

if opts.minibatch:
    km = MiniBatchKMeans(n_clusters=true_k, init='k-means++', n_init=1,
                         init_size=1000, batch_size=1000, verbose=opts.verbose)
else:
    km = KMeans(n_clusters=true_k, init='k-means++', max_iter=100, n_init=1,
                verbose=opts.verbose)

print("Clustering sparse data with %s" % km)
t0 = time()
km.fit(X)
print("done in %0.3fs" % (time() - t0))
print()

print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels, km.labels_))
print("Completeness: %0.3f" % metrics.completeness_score(labels, km.labels_))
print("V-measure: %0.3f" % metrics.v_measure_score(labels, km.labels_))
print("Adjusted Rand-Index: %.3f"
      % metrics.adjusted_rand_score(labels, km.labels_))
print("Silhouette Coefficient: %0.3f"
      % metrics.silhouette_score(X, km.labels_, sample_size=1000))

print()


if not opts.use_hashing:
    print("Top terms per cluster:")

    if opts.n_components:
        original_space_centroids = svd.inverse_transform(km.cluster_centers_)
        order_centroids = original_space_centroids.argsort()[:, ::-1]
    else:
        order_centroids = km.cluster_centers_.argsort()[:, ::-1]

    terms = vectorizer.get_feature_names()
    for i in range(true_k):
        print("Cluster %d:" % i, end='')
        for ind in order_centroids[i, :10]:
            print(' %s' % terms[ind], end='')
        print()

Tiempo total de ejecución del script: (0 minutos 1.189 segundos)

Galería generada por Sphinx-Gallery