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:
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 ...
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:
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 .
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)
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-')
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)
Si vous voulez l'implémenter vous-même, vous devez résoudre l'équation quadratique suivante:
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.
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:
Vous pouvez créer votre propre équation pour la limite:
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
:
en utilisant n=2
:
usng n=5
:
en utilisant n=7
:
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
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)
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)
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