Generación de datos sintéticos con Numpy y Scikit-Learn

    Introducción

    En este tutorial, discutiremos los detalles de la generación de diferentes conjuntos de datos sintéticos usando las bibliotecas Numpy y Scikit-learn. Veremos cómo se pueden generar diferentes muestras a partir de varias distribuciones con parámetros conocidos.

    También discutiremos la generación de conjuntos de datos para diferentes propósitos, como regresión, clasificación y agrupamiento. Al final, veremos cómo podemos generar un conjunto de datos que imite la distribución de un conjunto de datos existente.

    La necesidad de datos sintéticos

    En la ciencia de datos, los datos sintéticos juegan un papel muy importante. Nos permite probar un nuevo algoritmo en condiciones controladas. En otras palabras, podemos generar datos que prueben una propiedad o comportamiento muy específico de nuestro algoritmo.

    Por ejemplo, podemos probar su desempeño en conjuntos de datos balanceados vs. desequilibrados, o podemos evaluar su desempeño bajo diferentes niveles de ruido. Al hacer esto, podemos establecer una línea de base del rendimiento de nuestro algoritmo en varios escenarios.

    Hay muchos otros casos en los que se pueden necesitar datos sintéticos. Por ejemplo, los datos reales pueden ser difíciles o costosos de adquirir, o pueden tener muy pocos puntos de datos. Otra razón es la privacidad, donde los datos reales no se pueden revelar a otros.

    Configuración

    Antes de escribir código para la generación de datos sintéticos, importemos las bibliotecas necesarias:

    import numpy as np
    
    # Needed for plotting
    import matplotlib.colors
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    
    # Needed for generating classification, regression and clustering datasets
    import sklearn.datasets as dt
    
    # Needed for generating data from an existing dataset
    from sklearn.neighbors import KernelDensity
    from sklearn.model_selection import GridSearchCV
    

    Entonces, tendremos algunas variables útiles al principio:

    # Define the seed so that results can be reproduced
    seed = 11
    rand_state = 11
    
    # Define the color maps for plots
    color_map = plt.cm.get_cmap('RdYlBu')
    color_map_discrete = matplotlib.colors.LinearSegmentedColormap.from_list("", ["red","cyan","magenta","blue"])
    

    Generación de muestras 1D a partir de distribuciones conocidas

    Ahora, hablaremos sobre la generación de puntos de muestra a partir de distribuciones conocidas en 1D.

    los random módulo de numpy ofrece una amplia gama de formas de generar números aleatorios muestreados a partir de una distribución conocida con un conjunto fijo de parámetros. Para fines de reproducción, pasaremos el seed al RandomState call y siempre que usemos esa misma semilla, obtendremos los mismos números.

    Definamos una lista de distribución, como uniform, normal, exponential, etc., una lista de parámetros y una lista de colores para que podamos discernir visualmente entre estos:

    rand = np.random.RandomState(seed)    
    
    dist_list = ['uniform','normal','exponential','lognormal','chisquare','beta']
    param_list = ['-1,1','0,1','1','0,1','2','0.5,0.9']
    colors_list = ['green','blue','yellow','cyan','magenta','pink']
    

    Ahora, los empaquetaremos en subtramas de un Figure para su visualización y generar datos sintéticos en base a estas distribuciones, parámetros y asignarles colores adecuados.

    Esto se hace a través del eval() función, que usamos para generar una expresión de Python. Por ejemplo, podemos usar rand.exponential(1, 5000) para generar muestras a partir de una distribución exponencial de escala 1 y el tamaño de 5000.

    Aquí, usaremos nuestro dist_list, param_list y color_list para generar estas llamadas:

    fig,ax = plt.subplots(nrows=2, ncols=3,figsize=(12,7))
    plt_ind_list = np.arange(6)+231
    
    for dist, plt_ind, param, colors in zip(dist_list, plt_ind_list, param_list, colors_list):
        x = eval('rand.'+dist+'('+param+',5000)') 
        
        plt.subplot(plt_ind)
        plt.hist(x,bins=50,color=colors)
        plt.title(dist)
    
    fig.subplots_adjust(hspace=0.4,wspace=.3) 
    plt.suptitle('Sampling from Various Distributions',fontsize=20)
    plt.show()
    

    Esto resulta en:

    Datos sintéticos para regresión

    los paquete sklearn.datasets tiene funciones para generar conjuntos de datos sintéticos para regresión. Aquí, discutimos datos lineales y no lineales para regresión.

    los make_regression() La función devuelve un conjunto de puntos de datos de entrada (regresores) junto con su salida (objetivo). Esta función se puede ajustar con los siguientes parámetros:

    • n_features – número de dimensiones / características de los datos generados
    • noise – desviación estándar del ruido gaussiano
    • n_samples – número de muestras

    La variable de respuesta es una combinación lineal del conjunto de entrada generado.

    Una variable de respuesta es algo que depende de otras variables, en este caso particular, una característica de destino que estamos tratando de predecir utilizando todas las demás características de entrada.

    En el siguiente código, se han generado datos sintéticos para diferentes niveles de ruido y consta de dos características de entrada y una variable de destino. El color cambiante de los puntos de entrada muestra la variación en el valor del objetivo, correspondiente al punto de datos. Los datos se generan en 2D para una mejor visualización, pero se pueden crear datos de alta dimensión utilizando el n_features parámetro:

    map_colors = plt.cm.get_cmap('RdYlBu')
    fig,ax = plt.subplots(nrows=2, ncols=3,figsize=(16,7))
    plt_ind_list = np.arange(6)+231
    
    for noise,plt_ind in zip([0,0.1,1,10,100,1000],plt_ind_list): 
        x,y = dt.make_regression(n_samples=1000,
                                 n_features=2,
                                 noise=noise,
                                 random_state=rand_state) 
        
        plt.subplot(plt_ind)
        my_scatter_plot = plt.scatter(x[:,0],
                                      x[:,1],
                                      c=y,
                                      vmin=min(y),
                                      vmax=max(y),
                                      s=35,
                                      cmap=color_map)
        
        plt.title('noise: '+str(noise))
        plt.colorbar(my_scatter_plot)
        
    fig.subplots_adjust(hspace=0.3,wspace=.3)
    plt.suptitle('make_regression() With Different Noise Levels',fontsize=20)
    plt.show()
    

    Aquí, hemos creado un grupo de 1000 muestras, con dos variables de entrada (características). Según el nivel de ruido (0..1000), podemos ver cómo los datos generados difieren significativamente en el diagrama de dispersión:

    los make_friedman Familia de funciones

    Hay tres versiones del make_friedman?() función (reemplazar el ? con un valor de {1,2,3}).

    Estas funciones generan la variable de destino utilizando una combinación no lineal de las variables de entrada, como se detalla a continuación:

    • make_friedman1(): Los n_features El argumento de esta función debe ser al menos 5, por lo que se genera un número mínimo de 5 dimensiones de entrada. Aquí el objetivo viene dado por:
      $$
      y (x) = 10 * sin ( pi x_0 x_1) + 20 (x_2 – 0.5) ^ 2 + 10x_3 + 5x_4 + text {ruido}
      $$
    • make_friedman2(): Los datos generados tienen 4 dimensiones de entrada. La variable de respuesta viene dada por:

    $$
    y (x) = sqrt {(x_0 ^ 2 + x_1 x_2 – frac {1} {(x_1 x_3) ^ 2})} + text {ruido}
    $$

    • make_friedman3(): Los datos generados en este caso también tienen 4 dimensiones. La variable de salida viene dada por:

    $$
    y (x) = arctan ( frac {x_1 x_2 – frac {1} {(x_1 x_3)}} {x_0}) + text {ruido}
    $$

    El siguiente código genera los conjuntos de datos utilizando estas funciones y traza las tres primeras características en 3D, con colores que varían según la variable de destino:

    fig = plt.figure(figsize=(18,5))
    
    x,y = dt.make_friedman1(n_samples=1000,n_features=5,random_state=rand_state)
    ax = fig.add_subplot(131, projection='3d')
    my_scatter_plot = ax.scatter(x[:,0], x[:,1],x[:,2], c=y, cmap=color_map)
    fig.colorbar(my_scatter_plot)
    plt.title('make_friedman1')
    
    x,y = dt.make_friedman2(n_samples=1000,random_state=rand_state)
    ax = fig.add_subplot(132, projection='3d')
    my_scatter_plot = ax.scatter(x[:,0], x[:,1],x[:,2], c=y, cmap=color_map)
    fig.colorbar(my_scatter_plot)
    plt.title('make_friedman2')
    
    x,y = dt.make_friedman3(n_samples=1000,random_state=rand_state)
    ax = fig.add_subplot(133, projection='3d')
    my_scatter_plot = ax.scatter(x[:,0], x[:,1],x[:,2], c=y, cmap=color_map)
    fig.colorbar(my_scatter_plot)
    plt.suptitle('make_friedman?() for Non-Linear Data',fontsize=20)
    plt.title('make_friedman3')
    
    plt.show()
    

    Datos sintéticos para clasificación

    Scikit-learn tiene funciones simples y fáciles de usar para generar conjuntos de datos para la clasificación en el sklearn.dataset módulo. Repasemos un par de ejemplos.

    make_classification() para problemas de clasificación de clase n

    Para problemas de clasificación de clase n, el make_classification() La función tiene varias opciones:

    • class_sep: Especifica si las diferentes clases deben estar más dispersas y ser más fáciles de discriminar
    • n_features: Número de funciones
    • n_redundant: Número de funciones redundantes
    • n_repeated: Número de funciones repetidas
    • n_classes: Número total de clases

    Hagamos un conjunto de datos de clasificación para datos de entrada bidimensionales. Tendremos diferentes valores de class_sep para un problema de clasificación binaria. Los puntos del mismo color pertenecen a la misma clase. Vale la pena señalar que esta función también puede generar clases desequilibradas:

    fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
    plt_ind_list = np.arange(3)+131
    
    for class_sep,plt_ind in zip([0.1,1,10],plt_ind_list):
        x,y = dt.make_classification(n_samples=1000,
                                     n_features=2,
                                     n_repeated=0,
                                     class_sep=class_sep,
                                     n_redundant=0,
                                     random_state=rand_state)
        
        plt.subplot(plt_ind)
        my_scatter_plot = plt.scatter(x[:,0],
                                      x[:,1],
                                      c=y,
                                      vmin=min(y),
                                      vmax=max(y),
                                      s=35,
                                      cmap=color_map_discrete)
        plt.title('class_sep: '+str(class_sep))
    
    fig.subplots_adjust(hspace=0.3,wspace=.3)
    plt.suptitle('make_classification() With Different class_sep Values',fontsize=20)
    plt.show()
    

    make_multilabel_classification() para problemas de clasificación de etiquetas múltiples

    make_multilabel_classification() La función genera datos para problemas de clasificación de etiquetas múltiples. Tiene varias opciones, de las cuales la más destacable es n_label, que establece el número medio de etiquetas por punto de datos.

    Consideremos un problema de etiquetas múltiples de 4 clases, con el vector de etiquetas de destino convertido a un valor único para la visualización. Los puntos se colorean de acuerdo con la representación decimal del vector de etiqueta binaria. El código le ayudará a ver cómo usar un valor diferente para n_label, cambia la clasificación de un punto de datos generado:

    fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
    plt_ind_list = np.arange(3)+131
    
    for label,plt_ind in zip([2,3,4],plt_ind_list):
        x,y = dt.make_multilabel_classification(n_samples=1000,
                                                n_features=2,
                                                n_labels=label,
                                                n_classes=4,
                                                random_state=rand_state)
        target = np.sum(y*[8,4,2,1],axis=1)
        
        plt.subplot(plt_ind)
        my_scatter_plot = plt.scatter(x[:,0],
                                      x[:,1],
                                      c=target,
                                      vmin=min(target),
                                      vmax=max(target),
                                      cmap=color_map)
        plt.title('n_labels: '+str(label))
    
    fig.subplots_adjust(hspace=0.3,wspace=.3)
    plt.suptitle('make_multilabel_classification() With Different n_labels Values',fontsize=20)
    plt.show()
    

    Datos sintéticos para la agrupación en clústeres

    Para la agrupación, el sklearn.datasets proporciona varias opciones. Aquí, cubriremos el make_blobs() y make_circles() funciones.

    make_blobs()

    los make_blobs() La función genera datos a partir de distribuciones gaussianas isotrópicas. El número de características, el número de centros y la desviación estándar de cada grupo se pueden especificar como un argumento.

    Aquí, ilustramos esta función en 2D y mostramos cómo los puntos de datos cambian con diferentes valores de cluster_std parámetro:

    fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
    plt_ind_list = np.arange(3)+131
    
    for std,plt_ind in zip([0.5,1,10],plt_ind_list):
        x, label = dt.make_blobs(n_features=2,
                                 centers=4,
                                 cluster_std=std,
                                 random_state=rand_state)
        
        plt.subplot(plt_ind)    
        my_scatter_plot = plt.scatter(x[:,0],
                                      x[:,1],
                                      c=label,
                                      vmin=min(label),
                                      vmax=max(label),
                                      cmap=color_map_discrete)
        plt.title('cluster_std: '+str(std))
    
    fig.subplots_adjust(hspace=0.3,wspace=.3)
    plt.suptitle('make_blobs() With Different cluster_std Values',fontsize=20)
    plt.show()
    

    make_circles()

    los make_circles() La función genera dos círculos concéntricos con el mismo centro, uno dentro del otro.

    Usando el parámetro de ruido, se puede agregar distorsión a los datos generados. Este tipo de datos es útil para evaluar algoritmos de agrupación en clústeres basados ​​en afinidad. El siguiente código muestra los datos sintéticos generados a diferentes niveles de ruido:

    fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
    plt_ind_list = np.arange(3)+131
    
    for noise,plt_ind in zip([0,0.1,1],plt_ind_list):
        x, label = dt.make_circles(noise=noise,random_state=rand_state)
        
        plt.subplot(plt_ind)    
        my_scatter_plot = plt.scatter(x[:,0],
                                      x[:,1],
                                      c=label,
                                      vmin=min(label),
                                      vmax=max(label),
                                      cmap=color_map_discrete)
        plt.title('noise: '+str(noise))
    
    fig.subplots_adjust(hspace=0.3,wspace=.3)
    plt.suptitle('make_circles() With Different Noise Levels',fontsize=20)
    plt.show()
    

    Generar muestras derivadas de un conjunto de datos de entrada

    Hay muchas formas de generar muestras de datos adicionales a partir de un conjunto de datos existente. Aquí, ilustramos un método muy simple que primero estima la densidad del kernel de datos usando un kernel gaussiano y luego genera muestras adicionales de esta distribución.

    Para visualizar las muestras recién generadas, veamos el conjunto de datos de caras de Olivetti, recuperable a través de sklearn.datasets.fetch_olivetti_faces(). El conjunto de datos tiene 10 imágenes faciales diferentes de 40 personas diferentes.

    Esto es lo que haremos:

    • Obtener los datos de las caras
    • Genere el modelo de densidad del kernel a partir de datos
    • Utilice la densidad del kernel para generar nuevas muestras de datos
    • Muestre las caras originales y sintéticas.
    # Fetch the dataset and store in X
    faces = dt.fetch_olivetti_faces()
    X= faces.data
    
    # Fit a kernel density model using GridSearchCV to determine the best parameter for bandwidth
    bandwidth_params = {'bandwidth': np.arange(0.01,1,0.05)}
    grid_search = GridSearchCV(KernelDensity(), bandwidth_params)
    grid_search.fit(X)
    kde = grid_search.best_estimator_
    
    # Generate/sample 8 new faces from this dataset
    new_faces = kde.sample(8, random_state=rand_state)
    
    # Show a sample of 8 original face images and 8 generated faces derived from the faces dataset
    fig,ax = plt.subplots(nrows=2, ncols=8,figsize=(18,6),subplot_kw=dict(xticks=[], yticks=[]))
    for i in np.arange(8):
        ax[0,i].imshow(X[10*i,:].reshape(64,64),cmap=plt.cm.gray)   
        ax[1,i].imshow(new_faces[i,:].reshape(64,64),cmap=plt.cm.gray)    
    ax[0,3].set_title('Original Data',fontsize=20)
    ax[1,3].set_title('Synthetic Data',fontsize=20)
    fig.subplots_adjust(wspace=.1)
    plt.show()
    

    Las caras originales que se muestran aquí son una muestra de 8 caras elegidas de 400 imágenes, para tener una idea de cómo se ve el conjunto de datos original. Podemos generar tantos puntos de datos nuevos como queramos usando el sample() función.

    En este ejemplo, se generaron 8 nuevas muestras. Tenga en cuenta que las caras sintéticas que se muestran aquí no se corresponden necesariamente con la cara de la persona que se muestra arriba.

    Conclusiones

    En este artículo, conocimos algunos métodos para generar conjuntos de datos sintéticos para varios problemas. Los conjuntos de datos sintéticos nos ayudan a evaluar nuestros algoritmos en condiciones controladas y establecen una línea de base para las medidas de rendimiento.

    Python tiene una amplia gama de funciones que se pueden utilizar para la generación de datos artificiales. Es importante comprender qué funciones y API se pueden utilizar para sus requisitos específicos.

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *