Como plotar um conjunto de níveis tridimensionais em Python?
Tenho alguns problemas para traçar a imagem que está na minha cabeça. Quero visualizar o truque do kernel com Support Vector Machines. Então fiz alguns dados bidimensionais consistindo em dois círculos (um interno e um externo) que deveriam ser separados por um hiperplano. Obviamente, isso não é possível em duas dimensões - então eu as transformei em 3D. Seja n o número de amostras. Agora eu tenho um (n, 3) -array (3 colunas, n linhas) X de pontos de dados e um (n, 1) -array y com rótulos. Usando sklearn eu obtenho o classificador linear via
clf = svm.SVC(kernel='linear', C=1000)
clf.fit(X, y)
Eu já ploto os pontos de dados como gráfico de dispersão via
plt.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=plt.cm.Paired)
Agora eu quero plotar o hiperplano de separação como plotagem de superfície. Meu problema aqui é a ausência da representação explícita do hiperplano porque a função de decisão só produz um hiperplano implícito via decision_function = 0. Portanto, preciso traçar o conjunto de níveis (de nível 0) de um objeto quadridimensional.
Já que não sou um especialista em python, agradeceria se alguém pudesse me ajudar! E eu sei que esse não é realmente o "estilo" de usar um SVM, mas preciso dessa imagem como ilustração para minha tese.
Editar: meu "código" atual
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=['--','-','--'])
Resultado Desejado
Eu quero ter algo parecido com isso . Em geral, quero reconstruir o que eles fazem neste artigo , especialmente "Transformações não lineares".
Respostas
Parte da sua pergunta é abordada nesta pergunta sobre o SVM do kernel linear . É uma resposta parcial, porque apenas kernels lineares podem ser representados desta forma, ou seja, graças às coordenadas do hiperplano acessíveis através do estimador ao usar o kernel linear.
Outra solução é encontrar a isosuperfície com marching_cubes
Esta solução envolve a instalação do scikit-imagekit de ferramentas (https://scikit-image.org) que permite encontrar uma isosuperfície de um determinado valor (aqui, considerei 0, pois representa a distância até o hiperplano) da grade de malha das coordenadas 3D.
No código abaixo (copiado do seu), implementei a ideia para qualquer kernel (no exemplo, usei o kernel RBF) e a saída é mostrada abaixo do código. Por favor, considere minha nota de rodapé sobre plotagem 3D com matplotlib, que pode ser outro problema no seu 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')
A execução do código produz a seguinte imagem com Matplotlib, onde a superfície semitransparente verde representa o limite de decisão não linear.
Nota de rodapé: plotagem 3D com matplotlib
Observe que o Matplotlib 3D não é capaz de gerenciar a "profundidade" dos objetos em alguns casos, porque pode estar em conflito com o zorderdeste objeto. Esta é a razão pela qual às vezes o hiperplano parece estar plotado "em cima" dos pontos, mesmo que esteja "atrás". Esse problema é um bug conhecido discutido na documentação do matplotlib 3d e nesta resposta .
Se você deseja obter melhores resultados de renderização, pode usar o Mayavi , conforme recomendado pelos desenvolvedores do Matplotlib, ou qualquer outra biblioteca de plotagem 3D Python.