web-dev-qa-db-fra.com

Tracer le graphique du voisin le plus proche avec 8 caractéristiques?

Je suis nouveau dans l'apprentissage automatique et je voudrais configurer un petit échantillon en utilisant le k-nearest-Neighbor-method avec la bibliothèque PythonScikit.

La transformation et l'ajustement des données fonctionnent bien, mais je ne sais pas comment tracer un graphique montrant les points de données entourés de leur "voisinage".

Le jeu de données que j'utilise ressemble à ça:

Head of dataset. Il y a donc 8 fonctionnalités, plus une colonne "résultat".

D'après ma compréhension, j'obtiens un tableau montrant le euclidean-distances de tous les points de données, en utilisant le kneighbors_graph de Scikit. Donc, ma première tentative a été "simplement" de tracer cette matrice que j'obtiens à la suite de cette méthode. Ainsi:

def kneighbors_graph(self):
    self.X_train = self.X_train.values[:10,] #trimming down the data to only 10 entries
    A = neighbors.kneighbors_graph(self.X_train, 9, 'distance')
    plt.spy(A)
    plt.show()

Cependant, le graphique de résultat ne visualise pas vraiment la relation attendue entre les points de données. Result graph with kneighbors_graph - method

J'ai donc essayé d'ajuster l'exemple que vous pouvez trouver sur chaque page à propos de Scikit, le Iris_dataset. Malheureusement, il n'utilise que deux fonctionnalités, ce n'est donc pas exactement ce que je recherche, mais je voulais quand même obtenir au moins une première sortie:

  def plot_classification(self):
    h = .02
    n_neighbors = 9
    self.X = self.X.values[:10, [1,4]] #trim values to 10 entries and only columns 2 and 5 (indices 1, 4)
    self.y = self.y[:10, ] #trim outcome column, too

    clf = neighbors.KNeighborsClassifier(n_neighbors, weights='distance')
    clf.fit(self.X, self.y)

    x_min, x_max = self.X[:, 0].min() - 1, self.X[:, 0].max() + 1
    y_min, y_max = self.X[:, 1].min() - 1, self.X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) #no errors here, but it's  not moving on until computer crashes

    cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA','#00AAFF'])
    cmap_bold = ListedColormap(['#FF0000', '#00FF00','#00AAFF'])
    Z = Z.reshape(xx.shape)
    plt.figure()
    plt.pcolormesh(xx, yy, Z, cmap=cmap_light)
    plt.scatter(self.X[:, 0], self.X[:, 1], c=self.y, cmap=cmap_bold)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.title("Classification (k = %i)" % (n_neighbors))

Cependant, ce code ne fonctionne pas du tout et je ne peux pas comprendre pourquoi. Il ne se termine jamais, donc je n'ai pas d'erreurs avec lesquelles je pourrais travailler. Mon ordinateur se bloque juste après avoir attendu quelques minutes.

La ligne avec laquelle le code se débat est le Z = clf.predict (np.c_ [xx.ravel (), yy.ravel ()]) partie

Mes questions sont donc:

Premièrement, je ne comprends pas pourquoi j'aurais besoin de l'ajustement et de prédire pour avoir comploté les voisins. La distance euclidienne ne devrait-elle pas être suffisante pour tracer le graphique souhaité? (le graphique souhaité ressemble un peu à ceci: avoir deux couleurs pour le diabète ou non; flèche etc. pas nécessaire; crédit photo: ce tutoriel ).

desired graph

Où est mon erreur dans le code/pourquoi la prédiction de la partie se bloque-t-elle?

Existe-t-il un moyen de tracer les données avec toutes les fonctionnalités ? Je comprends que je ne peux pas avoir 8 axes, mais j'aimerais que la distance euclidienne soit calculée avec les 8 caractéristiques et pas seulement deux d'entre elles (avec deux, ce n'est pas très précis, n'est-ce pas?).

Mettre à jour

Voici un exemple de travail avec le code iris, mais mon jeu de données sur le diabète: il utilise les deux premières fonctionnalités de mon jeu de données. La seule différence que je peux voir avec mon code est la coupe du tableau -> ici, il prend les deux premières fonctionnalités, et je voulais les fonctionnalités 2 et 5, donc je le coupe différemment. Mais je ne comprends pas pourquoi le mien ne fonctionnerait pas. Voici donc le code de travail; copiez et collez-le, il fonctionne avec l'ensemble de données que j'ai fourni plus tôt:

from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import neighbors, datasets

diabetes = pd.read_csv('data/diabetes_data.csv')
columns_to_iterate = ['glucose', 'diastolic', 'triceps', 'insulin', 'bmi', 'dpf', 'age']
for column in columns_to_iterate:
    mean_value = diabetes[column].mean(skipna=True)
    diabetes = diabetes.replace({column: {0: mean_value}})
    diabetes[column] = diabetes[column].astype(np.float64)
X = diabetes.drop(columns=['diabetes'])
y = diabetes['diabetes'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
                                                                        random_state=1, stratify=y)
n_neighbors = 6

X = X.values[:, :2]
y = y
h = .02

cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#00AAFF'])
cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#00AAFF'])

clf = neighbors.KNeighborsClassifier(n_neighbors, weights='distance')
clf.fit(X, y)

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

Z = Z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("3-Class classification (k = %i)" % (n_neighbors))
plt.show()

Outcome of sample code

10
sonja

Table des matières:

  1. Relations entre les fonctionnalités
  2. Le graphique souhaité
  3. Pourquoi adapter et prévoir?
  4. Tracer 8 fonctionnalités?

Relations entre les fonctionnalités:

Le terme scientifique caractérisant la "relation" entre les caractéristiques est corrélation . Ce domaine est principalement exploré pendant PCA (Analyse en composantes principales) . L'idée est que toutes vos fonctionnalités ne sont pas importantes ou au moins certaines d'entre elles sont fortement corrélées. Considérez cela comme une similitude: si deux fonctionnalités sont fortement corrélées, elles incarnent les mêmes informations et, par conséquent, vous pouvez en supprimer une. En utilisant pandas cela ressemble à ceci:

import pandas as pd
import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt


def plot_correlation(data):
    '''
    plot correlation's matrix to explore dependency between features 
    '''
    # init figure size
    rcParams['figure.figsize'] = 15, 20
    fig = plt.figure()
    sns.heatmap(data.corr(), annot=True, fmt=".2f")
    plt.show()
    fig.savefig('corr.png')

# load your data 
data  = pd.read_csv('diabetes.csv')

# plot correlation & densities
plot_correlation(data)

La sortie est la matrice de corrélation suivante: enter image description here

Donc ici 1 signifie une corrélation totale et comme prévu, la diagonale est tout entière car une caractéristique est totalement corrélée avec elle-même. De plus, plus le nombre est bas, moins les caractéristiques sont corrélées.

Ici, nous devons considérer les corrélations de caractéristique à caractéristique et les corrélations de résultat à caractéristique. Entre les caractéristiques: des corrélations plus élevées signifient que nous pouvons supprimer l'une d'entre elles. Cependant, une corrélation élevée entre une fonctionnalité et le résultat signifie que la fonctionnalité est importante et contient beaucoup d'informations. Dans notre graphique, la dernière ligne représente la corrélation entre les caractéristiques et le résultat. Par conséquent, les valeurs les plus élevées/les caractéristiques les plus importantes sont "Glucose" (0,47) et "MBI" (0,29). De plus, la corrélation entre ces deux est relativement faible (0,22), ce qui signifie qu'ils ne sont pas similaires.

Nous pouvons vérifier ces résultats en utilisant les diagrammes de densité pour chaque entité en rapport avec le résultat. Ce n'est pas si complexe car nous n'avons que deux résultats: 0 ou 1. Donc, cela ressemblerait à ceci dans le code:

import pandas as pd
from pylab import rcParams
import matplotlib.pyplot as plt


def plot_densities(data):
    '''
    Plot features densities depending on the outcome values
    '''
    # change fig size to fit all subplots beautifully 
    rcParams['figure.figsize'] = 15, 20

    # separate data based on outcome values 
    outcome_0 = data[data['Outcome'] == 0]
    outcome_1 = data[data['Outcome'] == 1]

    # init figure
    fig, axs = plt.subplots(8, 1)
    fig.suptitle('Features densities for different outcomes 0/1')
    plt.subplots_adjust(left = 0.25, right = 0.9, bottom = 0.1, top = 0.95,
                        wspace = 0.2, hspace = 0.9)

    # plot densities for outcomes
    for column_name in names[:-1]: 
        ax = axs[names.index(column_name)]
        #plt.subplot(4, 2, names.index(column_name) + 1)
        outcome_0[column_name].plot(kind='density', ax=ax, subplots=True, 
                                    sharex=False, color="red", legend=True,
                                    label=column_name + ' for Outcome = 0')
        outcome_1[column_name].plot(kind='density', ax=ax, subplots=True, 
                                     sharex=False, color="green", legend=True,
                                     label=column_name + ' for Outcome = 1')
        ax.set_xlabel(column_name + ' values')
        ax.set_title(column_name + ' density')
        ax.grid('on')
    plt.show()
    fig.savefig('densities.png')

# load your data 
data  = pd.read_csv('diabetes.csv')
names = list(data.columns)

# plot correlation & densities
plot_densities(data)

La sortie est les graphiques de densité suivants: enter image description here

Dans les graphiques, lorsque les courbes verte et rouge sont presque identiques (se chevauchent), cela signifie que la fonction ne sépare pas les résultats. Dans le cas de l '"IMC", vous pouvez voir une certaine séparation (le léger décalage horizontal entre les deux courbes), et dans "Glucose", cela est beaucoup plus clair (cela est en accord avec les valeurs de corrélation).

=> La conclusion de ceci: si nous devons choisir seulement 2 fonctionnalités, alors 'Glucose' et 'MBI' sont ceux à choisir.

Le graphique souhaité

Je n'ai pas grand-chose à dire à ce sujet, sauf que le graphique représente une explication de base du concept de k-plus proche voisin. Ce n'est simplement pas une représentation de la classification.

Pourquoi ajuster et prévoir

Eh bien, c'est un concept d'apprentissage machine (ML) basique et essentiel. Vous avez un ensemble de données = [entrées, sorties_associées] et vous souhaitez créer un algorithme ML qui apprendra bien à relier les entrées à leurs sorties associées. Il s'agit d'une procédure en deux étapes. Au début, vous apprenez/apprenez à votre algorithme comment procéder. À ce stade, vous lui donnez simplement les entrées et les réponses comme vous le faites avec un enfant. La deuxième étape consiste à tester; maintenant que l'enfant a appris, vous voulez le tester. Vous lui donnez donc des informations similaires et vérifiez si ses réponses sont correctes. Maintenant, vous ne voulez pas lui donner les mêmes entrées qu'il a apprises car même si elle/il donne les bonnes réponses, il/elle peut simplement mémoriser les réponses de la phase d'apprentissage (cela s'appelle surapprentissage =) et donc elle/il n'a rien appris.

De la même manière que pour votre algorithme, vous divisez d'abord votre ensemble de données en données de formation et en données de test. Ensuite, vous ajustez vos données d'entraînement dans votre algorithme ou classificateur dans ce cas. C'est ce qu'on appelle la phase de formation. Après cela, vous testez la qualité de votre classificateur et s'il peut classer correctement les nouvelles données. C'est la phase de test. Sur la base des résultats des tests, vous évaluez les performances de votre classification en utilisant différentes évaluations-métriques comme la précision par exemple. La règle d'or ici est d'utiliser 2/3 des données pour la formation et 1/3 pour les tests.

Tracer 8 fonctionnalités?

La réponse simple est non, vous ne pouvez pas et si vous le pouvez, dites-moi comment.

La réponse amusante: pour visualiser 8 dimensions, c'est facile ... imaginez simplement n-dimensions puis laissez n = 8 ou visualisez simplement 3-D et crier 8 à elle.

La réponse logique: Nous vivons donc dans la Parole physique et les objets que nous voyons sont tridimensionnels, ce qui est techniquement une sorte de limite. Cependant, vous pouvez visualiser la 4ème dimension comme la couleur comme dans ici vous pouvez également utiliser le temps comme votre 5ème dimension et faire de votre intrigue une animation. @Rohan a suggéré dans ses formes de réponse mais son code n'a pas fonctionné pour moi, et je ne vois pas comment cela fournirait une bonne représentation des performances de l'algorithme. Quoi qu'il en soit, les couleurs, le temps, les formes ... après un certain temps, vous en manquez et vous vous retrouvez coincé. C'est l'une des raisons pour lesquelles les gens font de l'APC. Vous pouvez lire sur cet aspect du problème sous réduction de dimensionnalité .

Alors, que se passe-t-il si nous nous contentons de 2 fonctionnalités après l'ACP, puis que nous formons, testons, évaluons et tracons? .

Eh bien, vous pouvez utiliser le code suivant pour y parvenir:

import warnings 
import numpy as np
import pandas as pd
from pylab import rcParams
import matplotlib.pyplot as plt
from sklearn import neighbors
from matplotlib.colors import ListedColormap
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# filter warnings
warnings.filterwarnings("ignore")

def accuracy(k, X_train, y_train, X_test, y_test):
    '''
    compute accuracy of the classification based on k values 
    '''
    # instantiate learning model and fit data
    knn = KNeighborsClassifier(n_neighbors=k)    
    knn.fit(X_train, y_train)

    # predict the response
    pred = knn.predict(X_test)

    # evaluate and return  accuracy
    return accuracy_score(y_test, pred)

def classify_and_plot(X, y):
    ''' 
    split data, fit, classify, plot and evaluate results 
    '''
    # split data into training and testing set
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.33, random_state = 41)

    # init vars
    n_neighbors = 5
    h           = .02  # step size in the mesh

    # Create color maps
    cmap_light = ListedColormap(['#FFAAAA', '#AAAAFF'])
    cmap_bold  = ListedColormap(['#FF0000', '#0000FF'])

    rcParams['figure.figsize'] = 5, 5
    for weights in ['uniform', 'distance']:
        # we create an instance of Neighbours Classifier and fit the data.
        clf = neighbors.KNeighborsClassifier(n_neighbors, weights=weights)
        clf.fit(X_train, y_train)

        # Plot the decision boundary. For that, we will assign a color to each
        # point in the mesh [x_min, x_max]x[y_min, y_max].
        x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
        y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
        xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                             np.arange(y_min, y_max, h))
        Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

        # Put the result into a color plot
        Z = Z.reshape(xx.shape)
        fig = plt.figure()
        plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

        # Plot also the training points, x-axis = 'Glucose', y-axis = "BMI"
        plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold, edgecolor='k', s=20)   
        plt.xlim(xx.min(), xx.max())
        plt.ylim(yy.min(), yy.max())
        plt.title("0/1 outcome classification (k = %i, weights = '%s')" % (n_neighbors, weights))
        plt.show()
        fig.savefig(weights +'.png')

        # evaluate
        y_expected  = y_test
        y_predicted = clf.predict(X_test)

        # print results
        print('----------------------------------------------------------------------')
        print('Classification report')
        print('----------------------------------------------------------------------')
        print('\n', classification_report(y_expected, y_predicted))
        print('----------------------------------------------------------------------')
        print('Accuracy = %5s' % round(accuracy(n_neighbors, X_train, y_train, X_test, y_test), 3))
        print('----------------------------------------------------------------------')


# load your data 
data  = pd.read_csv('diabetes.csv')
names = list(data.columns)

# we only take the best two features and prepare them for the KNN classifier
rows_nbr = 30 # data.shape[0]
X_prime  = np.array(data.iloc[:rows_nbr, [1,5]])
X        = X_prime # preprocessing.scale(X_prime)
y        = np.array(data.iloc[:rows_nbr, 8])

# classify, evaluate and plot results
classify_and_plot(X, y)

Il en résulte les tracés suivants des limites de décision en utilisant les poids = 'uniforme' et les poids = 'distance' (pour lire la différence entre les deux, allez ici ):

enter image description hereenter image description here

Notez que: axe x = 'Glucose', axe y = 'IMC'

Améliorations:

Valeur K Quelle valeur k utiliser? combien de voisins à considérer. Des valeurs k faibles signifient moins de dépendance entre les données, mais des valeurs élevées signifient des durées d'exécution plus longues. C'est donc un compromis. Vous pouvez utiliser ce code pour trouver la valeur de k résultant en la plus grande précision:

best_n_neighbours = np.argmax(np.array([accuracy(k, X_train, y_train, X_test, y_test) for k in range(1, int(rows_nbr/2))])) + 1
print('For best accuracy use k = ', best_n_neighbours)

Utilisation de plus de données Ainsi, lorsque vous utilisez toutes les données, vous pourriez rencontrer des problèmes de mémoire (comme je l'ai fait) autre que le problème de sur-ajustement. Vous pouvez surmonter cela en prétraitant vos données. Considérez cela comme une mise à l'échelle et un formatage de vos données. Dans le code, utilisez simplement:

from sklearn import preprocessing 
X = preprocessing.scale(X_prime)

Le code complet peut être trouvé dans ce Gist

5
SuperKogito

Essayez ces deux simples morceaux de code, les deux tracés un graphique 3D avec 6 variables, tracer des données de dimension supérieure est toujours difficile, mais vous pouvez jouer avec et vérifier s'il peut être modifié pour obtenir le graphique de voisinage souhaité.

La première est assez intuitive, mais elle vous donne des rayons ou des boîtes aléatoires (cela dépend de votre nombre de variables) vous ne pouvez pas tracer plus de 6 variables, cela m'a toujours provoqué une erreur en utilisant plus de dimensions, mais vous devrez être assez créatif pour utiliser les deux autres variables. Cela aura du sens lorsque vous verrez le deuxième morceau de code.

premier morceau de code

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
X, Y, Z, U, V, W = Zip(*df)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.quiver(X, Y, Z, U, V, W)    
ax.set_xlim([-2, 2])
ax.set_ylim([-2, 2])
ax.set_zlim([-2, 2])
ax.legend()
plt.show()

deuxième morceau de code

ici j'utilise l'âge et l'IMC comme couleur et forme de vos points de données, vous pouvez à nouveau obtenir un graphique de voisinage pour 6 variables en peaufinant ce code et en utilisant les deux autres variables pour distinguer par couleur ou forme.

fig = plt.figure(figsize=(8, 6))
t = fig.suptitle('name_of_your_graph', fontsize=14)
ax = fig.add_subplot(111, projection='3d')

xs = list(df['pregnancies'])
ys = list(df['glucose'])
zs = list(df['bloodPressure'])
data_points = [(x, y, z) for x, y, z in Zip(xs, ys, zs)]

ss = list(df['skinThickness'])
colors = ['red' if age_group in range(0,35) else 'yellow' for age_group in list(df['age'])]
markers = [',' if q > 33 else 'x' if q in range(19,32) else 'o' for q in list(df['BMI'])]

for data, color, size, mark in Zip(data_points, colors, ss, markers):
    x, y, z = data
    ax.scatter(x, y, z, alpha=0.4, c=color, edgecolors='none', s=size, marker=mark)

ax.set_xlabel('pregnancies')
ax.set_ylabel('glucose')
ax.set_zlabel('bloodPressure')

Postez votre réponse. Je travaille sur un problème similaire qui peut être utile. Si dans le cas où vous ne pouviez pas tracer tous les 8-D, alors vous pouvez également tracer plusieurs graphiques de voisinage en utilisant une combinaison de 6 variables différentes à chaque fois.

5
Rohan Kumar