Comment tracer un jeu de niveaux en 3 dimensions Python?
J'ai du mal à tracer l'image qui est dans ma tête. Je veux visualiser l'astuce du noyau avec des machines vectorielles de support. J'ai donc créé des données bidimensionnelles constituées de deux cercles (un cercle intérieur et un cercle extérieur) qui devraient être séparés par un hyperplan. Évidemment, ce n'est pas possible en deux dimensions - je les ai donc transformées en 3D. Soit n le nombre d'échantillons. Maintenant, j'ai un (n, 3) -array (3 colonnes, n lignes) X de points de données et un (n, 1) -array y avec des étiquettes. En utilisant sklearn, j'obtiens le classificateur linéaire via
clf = svm.SVC(kernel='linear', C=1000)
clf.fit(X, y)
Je trace déjà les points de données sous forme de nuage de points via
plt.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=plt.cm.Paired)
Maintenant, je veux tracer l'hyperplan de séparation en tant que tracé de surface. Mon problème ici est la représentation explicite manquante de l'hyperplan car la fonction de décision ne produit qu'un hyperplan implicite via decision_function = 0. Par conséquent, j'ai besoin de tracer l'ensemble de niveaux (de niveau 0) d'un objet à 4 dimensions.
Comme je ne suis pas un expert en python, j'apprécierais que quelqu'un puisse m'aider! Et je sais que ce n'est pas vraiment le "style" d'utiliser un SVM mais j'ai besoin de cette image comme illustration pour ma thèse.
Edit: mon "code" actuel
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs, make_circles
from tikzplotlib import save as tikz_save
plt.close('all')
# we create 50 separable points
#X, y = make_blobs(n_samples=40, centers=2, random_state=6)
X, y = make_circles(n_samples=50, factor=0.5, random_state=4, noise=.05)
X2, y2 = make_circles(n_samples=50, factor=0.2, random_state=5, noise=.08)
X = np.append(X,X2, axis=0)
y = np.append(y,y2, axis=0)
# shifte X to [0,2]x[0,2]
X = np.array([[item[0] + 1, item[1] + 1] for item in X])
X[X<0] = 0.01
clf = svm.SVC(kernel='rbf', C=1000)
clf.fit(X, y)
plt.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=plt.cm.Paired)
# plot the decision function
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# create grid to evaluate model
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = clf.decision_function(xy).reshape(XX.shape)
# plot decision boundary and margins
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--','-','--'])
# plot support vectors
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100,
linewidth=1, facecolors='none', edgecolors='k')
################## KERNEL TRICK - 3D ##################
trans_X = np.array([[item[0]**2, item[1]**2, np.sqrt(2*item[0]*item[1])] for item in X])
fig = plt.figure()
ax = plt.axes(projection ="3d")
# creating scatter plot
ax.scatter3D(trans_X[:,0],trans_X[:,1],trans_X[:,2], c = y, cmap=plt.cm.Paired)
clf2 = svm.SVC(kernel='linear', C=1000)
clf2.fit(trans_X, y)
ax = plt.gca(projection='3d')
xlim = ax.get_xlim()
ylim = ax.get_ylim()
zlim = ax.get_zlim()
### from here i don't know what to do ###
xx = np.linspace(xlim[0], xlim[1], 3)
yy = np.linspace(ylim[0], ylim[1], 3)
zz = np.linspace(zlim[0], zlim[1], 3)
ZZ, YY, XX = np.meshgrid(zz, yy, xx)
xyz = np.vstack([XX.ravel(), YY.ravel(), ZZ.ravel()]).T
Z = clf2.decision_function(xyz).reshape(XX.shape)
#ax.contour(XX, YY, ZZ, Z, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--','-','--'])
Sortie désirée
Je veux obtenir quelque chose comme ça . En général, je veux reconstruire ce qu'ils font dans cet article , en particulier "Transformations non linéaires".
Réponses
Une partie de votre question est abordée dans cette question sur SVM à noyau linéaire . C'est une réponse partielle, car seuls les noyaux linéaires peuvent être représentés de cette façon, c'est-à-dire grâce aux coordonnées hyperplan accessibles via l'estimateur lors de l'utilisation du noyau linéaire.
Une autre solution est de trouver l'isosurface avec marching_cubes
Cette solution consiste à installer la scikit-imageboîte à outils (https://scikit-image.org) qui permet de trouver une isosurface d'une valeur donnée (ici, j'ai considéré 0 car elle représente la distance à l'hyperplan) à partir de la grille de maillage des coordonnées 3D.
Dans le code ci-dessous (copié à partir du vôtre), j'implémente l'idée de n'importe quel noyau (dans l'exemple, j'ai utilisé le noyau RBF), et la sortie est affichée sous le code. Veuillez considérer ma note de bas de page sur le traçage 3D avec matplotlib, qui peut être un autre problème dans votre cas.
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from skimage import measure
from sklearn.datasets import make_blobs, make_circles
from tikzplotlib import save as tikz_save
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
plt.close('all')
# we create 50 separable points
#X, y = make_blobs(n_samples=40, centers=2, random_state=6)
X, y = make_circles(n_samples=50, factor=0.5, random_state=4, noise=.05)
X2, y2 = make_circles(n_samples=50, factor=0.2, random_state=5, noise=.08)
X = np.append(X,X2, axis=0)
y = np.append(y,y2, axis=0)
# shifte X to [0,2]x[0,2]
X = np.array([[item[0] + 1, item[1] + 1] for item in X])
X[X<0] = 0.01
clf = svm.SVC(kernel='rbf', C=1000)
clf.fit(X, y)
plt.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=plt.cm.Paired)
# plot the decision function
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# create grid to evaluate model
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = clf.decision_function(xy).reshape(XX.shape)
# plot decision boundary and margins
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--','-','--'])
# plot support vectors
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100,
linewidth=1, facecolors='none', edgecolors='k')
################## KERNEL TRICK - 3D ##################
trans_X = np.array([[item[0]**2, item[1]**2, np.sqrt(2*item[0]*item[1])] for item in X])
fig = plt.figure()
ax = plt.axes(projection ="3d")
# creating scatter plot
ax.scatter3D(trans_X[:,0],trans_X[:,1],trans_X[:,2], c = y, cmap=plt.cm.Paired)
clf2 = svm.SVC(kernel='rbf', C=1000)
clf2.fit(trans_X, y)
z = lambda x,y: (-clf2.intercept_[0]-clf2.coef_[0][0]*x-clf2.coef_[0][1]*y) / clf2.coef_[0][2]
ax = plt.gca(projection='3d')
xlim = ax.get_xlim()
ylim = ax.get_ylim()
zlim = ax.get_zlim()
### from here i don't know what to do ###
xx = np.linspace(xlim[0], xlim[1], 50)
yy = np.linspace(ylim[0], ylim[1], 50)
zz = np.linspace(zlim[0], zlim[1], 50)
XX ,YY, ZZ = np.meshgrid(xx, yy, zz)
xyz = np.vstack([XX.ravel(), YY.ravel(), ZZ.ravel()]).T
Z = clf2.decision_function(xyz).reshape(XX.shape)
# find isosurface with marching cubes
dx = xx[1] - xx[0]
dy = yy[1] - yy[0]
dz = zz[1] - zz[0]
verts, faces, _, _ = measure.marching_cubes_lewiner(Z, 0, spacing=(1, 1, 1), step_size=2)
verts *= np.array([dx, dy, dz])
verts -= np.array([xlim[0], ylim[0], zlim[0]])
# add as Poly3DCollection
mesh = Poly3DCollection(verts[faces])
mesh.set_facecolor('g')
mesh.set_edgecolor('none')
mesh.set_alpha(0.3)
ax.add_collection3d(mesh)
ax.view_init(20, -45)
plt.savefig('kerneltrick')
L'exécution du code produit l'image suivante avec Matplotlib, où la surface semi-transparente verte représente la limite de décision non linéaire.
Note de bas de page: tracé 3D avec matplotlib
Notez que Matplotlib 3D n'est pas en mesure de gérer la "profondeur" des objets dans certains cas, car il peut être en conflit avec celui zorderde cet objet. C'est la raison pour laquelle parfois l'hyperplan semble être tracé "au-dessus" des points, même s'il devrait être "derrière". Ce problème est un bogue connu discuté dans la documentation matplotlib 3d et dans cette réponse .
Si vous souhaitez obtenir de meilleurs résultats de rendu, vous pouvez utiliser Mayavi , comme recommandé par les développeurs Matplotlib, ou toute autre bibliothèque de traçage 3D Python.