¿Cómo trazar un conjunto de niveles tridimensional de Python?

Sep 30 2020

Tengo algunos problemas para trazar la imagen que tengo en la cabeza. Quiero visualizar el truco del kernel con Support Vector Machines. Así que hice algunos datos bidimensionales que constan de dos círculos (un círculo interior y otro exterior) que deberían estar separados por un hiperplano. Obviamente, esto no es posible en dos dimensiones, así que las transformé en 3D. Sea n el número de muestras. Ahora tengo un (n, 3) -array (3 columnas, n filas) X de puntos de datos y un (n, 1) -array y con etiquetas. Usando sklearn obtengo el clasificador lineal a través de

clf = svm.SVC(kernel='linear', C=1000)
clf.fit(X, y)

Ya tracé los puntos de datos como diagrama de dispersión a través de

plt.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=plt.cm.Paired)

Ahora quiero trazar el hiperplano de separación como trama de superficie. Mi problema aquí es la falta de representación explícita del hiperplano porque la función de decisión solo produce una vía de hiperplano implícito decision_function = 0. Por lo tanto, necesito trazar el conjunto de niveles (de nivel 0) de un objeto de 4 dimensiones.

Como no soy un experto en pitones, agradecería que alguien me ayudara. Y sé que este no es realmente el "estilo" de usar una SVM, pero necesito esta imagen como ilustración para mi tesis.

Editar: mi "código" actual

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=['--','-','--'])

Salida deseada

Quiero conseguir algo como eso . En general quiero reconstruir lo que hacen en este artículo , especialmente "Transformaciones no lineales".

Respuestas

2 Léonard Sep 30 2020 at 15:48

Parte de su pregunta se aborda en esta pregunta sobre SVM de núcleo lineal . Es una respuesta parcial, porque solo los núcleos lineales se pueden representar de esta manera, es decir, gracias a las coordenadas del hiperplano accesibles a través del estimador cuando se usa el núcleo lineal.

Otra solución es encontrar la isosuperficie con marching_cubes

Esta solución implica la instalación del scikit-imagekit de herramientas (https://scikit-image.org) que permite encontrar una isosuperficie de un valor dado (aquí, consideré 0 ya que representa la distancia al hiperplano) de la cuadrícula de malla de las coordenadas 3D.

En el siguiente código (copiado del suyo), implemento la idea para cualquier kernel (en el ejemplo, usé el kernel RBF), y la salida se muestra debajo del código. Considere mi nota al pie sobre el trazado en 3D con matplotlib, que puede ser otro problema en su caso.

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')

La ejecución del código produce la siguiente imagen con Matplotlib, donde la superficie verde semitransparente representa el límite de decisión no lineal.

Nota al pie: trazado en 3D con matplotlib

Tenga en cuenta que Matplotlib 3D no puede administrar la "profundidad" de los objetos en algunos casos, porque puede estar en conflicto con la zorderde este objeto. Esta es la razón por la que a veces el hiperplano parece estar trazado "encima" de los puntos, incluso debería estar "detrás". Este problema es un error conocido discutido en la documentación de matplotlib 3d y en esta respuesta .

Si desea obtener mejores resultados de renderizado, puede utilizar Mayavi , como recomiendan los desarrolladores de Matplotlib, o cualquier otra biblioteca de trazado de Python 3D.