Esta resposta foi significativamente modificada de sua forma original. As falhas da minha resposta original serão discutidas abaixo, mas se você quiser ver mais ou menos como era essa resposta antes de fazer a grande edição, dê uma olhada no seguinte caderno: https://nbviewer.jupyter.org/github /dmarx/data_generation_demo/blob/54be78fb5b68218971d2568f1680b4f783c0a79a/demo.ipynb
P(X)P(X|Y)∝P(Y|X)P(X)P(Y|X)X
Estimativa de máxima verossimilhança
... e por que não funciona aqui
Na minha resposta original, a técnica que sugeri foi usar o MCMC para realizar a estimativa da máxima probabilidade. Geralmente, o MLE é uma boa abordagem para encontrar as soluções "ótimas" para probabilidades condicionais, mas temos um problema aqui: porque estamos usando um modelo discriminativo (uma floresta aleatória neste caso), nossas probabilidades estão sendo calculadas em relação aos limites de decisão . Na verdade, não faz sentido falar sobre uma solução "ideal" para um modelo como esse, porque, quando nos afastamos o suficiente dos limites da classe, o modelo apenas prevê aqueles para tudo. Se tivermos classes suficientes, algumas delas podem estar completamente "cercadas"; nesse caso, isso não será um problema, mas as classes nos limites de nossos dados serão "maximizadas" por valores que não são necessariamente viáveis.
Para demonstrar, vou aproveitar alguns códigos de conveniência que você pode encontrar aqui , que fornece a GenerativeSampler
classe que agrupa o código da minha resposta original, algum código adicional para esta melhor solução e alguns recursos adicionais com os quais eu estava brincando (alguns que funcionam , alguns que não) que eu provavelmente não entrarei aqui.
np.random.seed(123)
sampler = GenerativeSampler(model=RFC, X=X, y=y,
target_class=2,
prior=None,
class_err_prob=0.05, # <-- the score we use for candidates that aren't predicted as the target class
rw_std=.05, # <-- controls the step size of the random walk proposal
verbose=True,
use_empirical=False)
samples, _ = sampler.run_chain(n=5000)
burn = 1000
thin = 20
X_s = pca.transform(samples[burn::thin,:])
# Plot the iris data
col=['r','b','g']
for i in range(3):
plt.scatter(*X_r[y==i,:].T, c=col[i], marker='x')
plt.plot(*X_s.T, 'k')
plt.scatter(*X_s.T, c=np.arange(X_s.shape[0]))
plt.colorbar()
plt.show()
Nesta visualização, os x são os dados reais e a classe em que estamos interessados é verde. Os pontos conectados à linha são as amostras que desenhamos e sua cor corresponde à ordem em que foram amostradas, com sua posição de sequência "reduzida" dada pelo rótulo da barra de cores à direita.
Como você pode ver, o amostrador divergiu dos dados com bastante rapidez e, basicamente, fica muito distante dos valores do espaço de recurso que correspondem a quaisquer observações reais. Claramente, isso é um problema.
Uma maneira de enganar é alterar a função da proposta para permitir apenas que os recursos obtenham valores que realmente observamos nos dados. Vamos tentar isso e ver como isso muda o comportamento do nosso resultado.
np.random.seed(123)
sampler = GenerativeSampler(model=RFC, X=X, y=y,
target_class=2,
prior=None,
class_err_prob=0.05,
verbose=True,
use_empirical=True) # <-- magic happening under the hood
samples, _ = sampler.run_chain(n=5000)
X_s = pca.transform(samples[burn::thin,:])
# Constrain attention to just the target class this time
i=2
plt.scatter(*X_r[y==i,:].T, c='k', marker='x')
plt.scatter(*X_s.T, c='g', alpha=0.3)
#plt.colorbar()
plt.show()
sns.kdeplot(X_s, cmap=sns.dark_palette('green', as_cmap=True))
plt.scatter(*X_r[y==i,:].T, c='k', marker='x')
plt.show()
X
P(X)P(Y|X)P(X)P(Y|X)P(X)
Digite a regra de Bayes
Depois que você me perseguiu para ser menos exigente com a matemática aqui, brinquei bastante com isso (daí eu construí a GenerativeSampler
coisa), e encontrei os problemas que expus acima. Eu me senti muito, muito estúpido quando fiz isso, mas obviamente o que você está pedindo pede uma aplicação da regra de Bayes e peço desculpas por ter sido menosprezado antes.
Se você não está familiarizado com a regra de bayes, fica assim:
P(B|A)=P(A|B)P(B)P(A)
Em muitas aplicações, o denominador é uma constante que atua como um termo de escala para garantir que o numerador se integre a 1; portanto, a regra geralmente é reafirmada da seguinte maneira:
P(B|A)∝P(A|B)P(B)
Ou em inglês simples: "o posterior é proporcional aos tempos anteriores à probabilidade".
Parece familiar? Que tal agora:
P(X|Y)∝P(Y|X)P(X)
Sim, foi exatamente isso que desenvolvemos anteriormente, construindo uma estimativa para o MLE ancorada na distribuição observada dos dados. Eu nunca pensei sobre Bayes governar dessa maneira, mas faz sentido, então obrigado por me dar a oportunidade de descobrir essa nova perspectiva.
P(Y)
Portanto, depois de entendermos que precisamos incorporar um prior para os dados, vamos fazer isso ajustando um KDE padrão e ver como isso muda nosso resultado.
np.random.seed(123)
sampler = GenerativeSampler(model=RFC, X=X, y=y,
target_class=2,
prior='kde', # <-- the new hotness
class_err_prob=0.05,
rw_std=.05, # <-- back to the random walk proposal
verbose=True,
use_empirical=False)
samples, _ = sampler.run_chain(n=5000)
burn = 1000
thin = 20
X_s = pca.transform(samples[burn::thin,:])
# Plot the iris data
col=['r','b','g']
for i in range(3):
plt.scatter(*X_r[y==i,:].T, c=col[i], marker='x')
plt.plot(*X_s.T, 'k--')
plt.scatter(*X_s.T, c=np.arange(X_s.shape[0]), alpha=0.2)
plt.colorbar()
plt.show()
XP(X|Y)
# MAP estimation
from sklearn.neighbors import KernelDensity
from sklearn.model_selection import GridSearchCV
from scipy.optimize import minimize
grid = GridSearchCV(KernelDensity(), {'bandwidth': np.linspace(0.1, 1.0, 30)}, cv=10, refit=True)
kde = grid.fit(samples[burn::thin,:]).best_estimator_
def map_objective(x):
try:
score = kde.score_samples(x)
except ValueError:
score = kde.score_samples(x.reshape(1,-1))
return -score
x_map = minimize(map_objective, samples[-1,:].reshape(1,-1)).x
print(x_map)
x_map_r = pca.transform(x_map.reshape(1,-1))[0]
col=['r','b','g']
for i in range(3):
plt.scatter(*X_r[y==i,:].T, c=col[i], marker='x')
sns.kdeplot(*X_s.T, cmap=sns.dark_palette('green', as_cmap=True))
plt.scatter(x_map_r[0], x_map_r[1], c='k', marker='x', s=150)
plt.show()
E aí está: o grande X preto é a nossa estimativa MAP (esses contornos são o KDE do posterior).