web-dev-qa-db-fra.com

Tracé d'une frontière de décision séparant 2 classes à l'aide du pyplot de Matplotlib

Je pourrais vraiment utiliser un conseil pour m'aider à tracer une limite de décision à séparer en classes de données. J'ai créé des exemples de données (d'une distribution gaussienne) via Python NumPy. Dans ce cas, chaque point de données est une coordonnée 2D, c’est-à-dire un vecteur à 1 colonne composé de 2 lignes. Par exemple.,

[ 1
  2 ]

Supposons que j'ai 2 classes, class1 et class2, et que j'ai créé 100 points de données pour class1 et 100 points de données pour class2 via le code ci-dessous (affecté aux variables x1_samples et x2_samples).

mu_vec1 = np.array([0,0])
cov_mat1 = np.array([[2,0],[0,2]])
x1_samples = np.random.multivariate_normal(mu_vec1, cov_mat1, 100)
mu_vec1 = mu_vec1.reshape(1,2).T # to 1-col vector

mu_vec2 = np.array([1,2])
cov_mat2 = np.array([[1,0],[0,1]])
x2_samples = np.random.multivariate_normal(mu_vec2, cov_mat2, 100)
mu_vec2 = mu_vec2.reshape(1,2).T

Lorsque je trace les points de données pour chaque classe, cela ressemble à ceci:

enter image description here

Maintenant, je suis venu avec une équation pour une frontière de décision pour séparer les deux classes et voudrais l'ajouter à l'intrigue. Cependant, je ne suis pas vraiment sûr de comment tracer cette fonction:

def decision_boundary(x_vec, mu_vec1, mu_vec2):
    g1 = (x_vec-mu_vec1).T.dot((x_vec-mu_vec1))
    g2 = 2*( (x_vec-mu_vec2).T.dot((x_vec-mu_vec2)) )
    return g1 - g2

J'apprécierais vraiment toute aide!

EDIT: Intuitivement (si je faisais mes calculs correctement) Je m'attendrais à ce que la limite de décision ressemble un peu à cette ligne rouge lorsque je trace la fonction ...

enter image description here

34
Sebastian

C'étaient de bonnes suggestions, merci beaucoup pour votre aide! J'ai fini par résoudre l'équation de manière analytique et c'est la solution à laquelle j'ai abouti (je veux juste la poster pour référence future:

enter image description here

Et le code peut être trouvé ici

MODIFIER:

J'ai également une fonction pratique pour tracer des régions de décision pour les classificateurs qui implémentent les méthodes fit et predict, par exemple les classificateurs de scikit-learn, qui sont utiles si la solution ne peut pas être trouvée de manière analytique. Vous trouverez une description plus détaillée de son fonctionnement ici .

enter image description here

7
Sebastian

Votre question est plus compliquée qu'un complot simple: vous devez dessiner le contour qui maximisera la distance entre les classes. Heureusement, c'est un domaine bien étudié, en particulier pour l'apprentissage par machine SVM.

La méthode la plus simple consiste à télécharger le module scikit-learn, qui fournit de nombreuses méthodes intéressantes pour tracer des limites: http://scikit-learn.org/stable/modules/svm.html

Code:

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib
from matplotlib import pyplot as plt
import scipy
from sklearn import svm


mu_vec1 = np.array([0,0])
cov_mat1 = np.array([[2,0],[0,2]])
x1_samples = np.random.multivariate_normal(mu_vec1, cov_mat1, 100)
mu_vec1 = mu_vec1.reshape(1,2).T # to 1-col vector

mu_vec2 = np.array([1,2])
cov_mat2 = np.array([[1,0],[0,1]])
x2_samples = np.random.multivariate_normal(mu_vec2, cov_mat2, 100)
mu_vec2 = mu_vec2.reshape(1,2).T


fig = plt.figure()


plt.scatter(x1_samples[:,0],x1_samples[:,1], marker='+')
plt.scatter(x2_samples[:,0],x2_samples[:,1], c= 'green', marker='o')

X = np.concatenate((x1_samples,x2_samples), axis = 0)
Y = np.array([0]*100 + [1]*100)

C = 1.0  # SVM regularization parameter
clf = svm.SVC(kernel = 'linear',  gamma=0.7, C=C )
clf.fit(X, Y)

Tracé linéaire (extrait de http://scikit-learn.org/stable/auto_examples/svm/plot_svm_margin.html )


w = clf.coef_[0]
a = -w[0] / w[1]
xx = np.linspace(-5, 5)
yy = a * xx - (clf.intercept_[0]) / w[1]

plt.plot(xx, yy, 'k-')

enter image description here

Tracé multilinéaire (extrait de http://scikit-learn.org/stable/auto_examples/svm/plot_iris.html )


C = 1.0  # SVM regularization parameter
clf = svm.SVC(kernel = 'rbf',  gamma=0.7, C=C )
clf.fit(X, Y)

h = .02  # step size in the mesh
# create a mesh to plot in
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))


# Plot the decision boundary. For that, we will assign a color to each
# point in the mesh [x_min, m_max]x[y_min, y_max].
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

# Put the result into a color plot
Z = Z.reshape(xx.shape)
plt.contour(xx, yy, Z, cmap=plt.cm.Paired)

enter image description here

La mise en oeuvre

Si vous voulez l'implémenter vous-même, vous devez résoudre l'équation quadratique suivante: boundary equation

L'article de Wikipédia

Malheureusement, pour les limites non linéaires comme celle que vous tracez, c’est un problème difficile qui repose sur une astuce du noyau, mais il n’existe pas de solution claire.

32
lucasg

En fonction de la manière dont vous avez écrit decision_boundary, vous souhaiterez utiliser la fonction contour, comme l'a noté Joe ci-dessus. Si vous voulez seulement la ligne de démarcation, vous pouvez dessiner un seul contour au niveau 0:

f, ax = plt.subplots(figsize=(7, 7))
c1, c2 = "#3366AA", "#AA3333"
ax.scatter(*x1_samples.T, c=c1, s=40)
ax.scatter(*x2_samples.T, c=c2, marker="D", s=40)
x_vec = np.linspace(*ax.get_xlim())
ax.contour(x_vec, x_vec,
           decision_boundary(x_vec, mu_vec1, mu_vec2),
           levels=[0], cmap="Greys_r")

Ce qui rend:

enter image description here

16
mwaskom

Vous pouvez créer votre propre équation pour la limite:

enter image description here

où vous devez trouver les positions x0 et y0, ainsi que les constantes ai et bi pour l’équation du radius. Donc, vous avez des variables 2*(n+1)+2. Utiliser scipy.optimize.leastsq est simple pour ce type de problème.

Le code joint ci-dessous crée le résidu pour leastsq pénalisant les points surdimensionnant la limite. Le résultat de votre problème, obtenu avec:

x, y = find_boundary(x2_samples[:,0], x2_samples[:,1], n)
ax.plot(x, y, '-k', lw=2.)

x, y = find_boundary(x1_samples[:,0], x1_samples[:,1], n)
ax.plot(x, y, '--k', lw=2.)

en utilisant n=1: enter image description here

en utilisant n=2:

enter image description here

usng n=5: enter image description here

en utilisant n=7:

enter image description here

import numpy as np
from numpy import sin, cos, pi
from scipy.optimize import leastsq

def find_boundary(x, y, n, plot_pts=1000):

    def sines(theta):
        ans = np.array([sin(i*theta)  for i in range(n+1)])
        return ans

    def cosines(theta):
        ans = np.array([cos(i*theta)  for i in range(n+1)])
        return ans

    def residual(params, x, y):
        x0 = params[0]
        y0 = params[1]
        c = params[2:]

        r_pts = ((x-x0)**2 + (y-y0)**2)**0.5

        thetas = np.arctan2((y-y0), (x-x0))
        m = np.vstack((sines(thetas), cosines(thetas))).T
        r_bound = m.dot(c)

        delta = r_pts - r_bound
        delta[delta>0] *= 10

        return delta

    # initial guess for x0 and y0
    x0 = x.mean()
    y0 = y.mean()

    params = np.zeros(2 + 2*(n+1))
    params[0] = x0
    params[1] = y0
    params[2:] += 1000

    popt, pcov = leastsq(residual, x0=params, args=(x, y),
                         ftol=1.e-12, xtol=1.e-12)

    thetas = np.linspace(0, 2*pi, plot_pts)
    m = np.vstack((sines(thetas), cosines(thetas))).T
    c = np.array(popt[2:])
    r_bound = m.dot(c)
    x_bound = x0 + r_bound*cos(thetas)
    y_bound = y0 + r_bound*sin(thetas)

    return x_bound, y_bound
8

Vient de résoudre un problème très similaire avec une approche différente (recherche de racine) et je voulais poster cette alternative comme réponse ici pour référence future:

   def discr_func(x, y, cov_mat, mu_vec):
        """
        Calculates the value of the discriminant function for a dx1 dimensional
        sample given covariance matrix and mean vector.

        Keyword arguments:
            x_vec: A dx1 dimensional numpy array representing the sample.
            cov_mat: numpy array of the covariance matrix.
            mu_vec: dx1 dimensional numpy array of the sample mean.

        Returns a float value as result of the discriminant function.

        """
        x_vec = np.array([[x],[y]])

        W_i = (-1/2) * np.linalg.inv(cov_mat)
        assert(W_i.shape[0] > 1 and W_i.shape[1] > 1), 'W_i must be a matrix'

        w_i = np.linalg.inv(cov_mat).dot(mu_vec)
        assert(w_i.shape[0] > 1 and w_i.shape[1] == 1), 'w_i must be a column vector'

        omega_i_p1 = (((-1/2) * (mu_vec).T).dot(np.linalg.inv(cov_mat))).dot(mu_vec)
        omega_i_p2 = (-1/2) * np.log(np.linalg.det(cov_mat))
        omega_i = omega_i_p1 - omega_i_p2
        assert(omega_i.shape == (1, 1)), 'omega_i must be a scalar'

        g = ((x_vec.T).dot(W_i)).dot(x_vec) + (w_i.T).dot(x_vec) + omega_i
        return float(g)

    #g1 = discr_func(x, y, cov_mat=cov_mat1, mu_vec=mu_vec_1)
    #g2 = discr_func(x, y, cov_mat=cov_mat2, mu_vec=mu_vec_2)

    x_est50 = list(np.arange(-6, 6, 0.1))
    y_est50 = []
    for i in x_est50:
        y_est50.append(scipy.optimize.bisect(lambda y: discr_func(i, y, cov_mat=cov_est_1, mu_vec=mu_est_1) - \
                          discr_func(i, y, cov_mat=cov_est_2, mu_vec=mu_est_2), -10,10))
    y_est50 = [float(i) for i in y_est50]

Voici le résultat: (Bleu le cas quadratique, rouge le cas linéaire (variances égales) http://i.imgur.com/T16awxM.png?1

1
Sebastian

J'aime que la bibliothèque mglearn trace des limites de décision. Voici un exemple tiré du livre "Introduction à l’apprentissage automatique en Python" de A. Mueller:

fig, axes = plt.subplots(1, 3, figsize=(10, 3))
for n_neighbors, ax in Zip([1, 3, 9], axes):
    clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)
    mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title("{} neighbor(s)".format(n_neighbors))
    ax.set_xlabel("feature 0")
    ax.set_ylabel("feature 1")
axes[0].legend(loc=3)

 enter image description here

0
Junior Koch

Je sais que cette question a reçu une réponse analytique très approfondie. Je voulais juste partager un possible "bidouillage" au problème. Il est difficile à manier mais fait le travail.

Commencez par créer une grille de maillage de la zone 2d, puis, sur la base du classificateur, créez une carte de classes de tout l’espace. Détectez ensuite les modifications de la décision prise par rangée, stockez les points de contour dans une liste et étalez les points sous forme de graphique. 

def disc(x):   # returns the class of the point based on location x = [x,y]
     temp = 0.5  + 0.5*np.sign(disc0(x)-disc1(x)) 
# disc0() and disc1() are the discriminant functions of the respective classes
     return 0*temp + 1*(1-temp) 

num = 200
a = np.linspace(-4,4,num)
b = np.linspace(-6,6,num)
X,Y = np.meshgrid(a,b)

def decColor(x,y):
     temp = np.zeros((num,num))
     print x.shape, np.size(x,axis=0)
     for l in range(num):
         for m in range(num):
             p = np.array([x[l,m],y[l,m]])
             #print p
             temp[l,m] = disc(p)
     return temp
boundColorMap = decColor(X,Y)

group = 0
boundary = []
for x in range(num):
    group = boundColorMap[x,0]
    for y in range(num):
        if boundColorMap[x,y]!=group:
            boundary.append([X[x,y],Y[x,y]])
            group = boundColorMap[x,y]  
boundary = np.array(boundary)

Exemple de limite de décision pour un classificateur simple à deux variables gaussien

0
Krishna Praveen