Ця відповідь була суттєво модифікована з початкової форми. Про недоліки моєї оригінальної відповіді буде обговорено нижче, але якщо ви хочете приблизно побачити, як виглядала ця відповідь до того, як я зробив велику редагування, подивіться на наступний зошит: https://nbviewer.jupyter.org/github /dmarx/data_generation_demo/blob/54be78fb5b68218971d2568f1680b4f783c0a79a/demo.ipynb
TL; DR: Використовуйте KDE (або обрану вами процедуру) для апроксимації , а потім використовуйте MCMC для отримання зразків з , де задається вашою моделлю. З цих зразків ви можете оцінити "оптимальний" , встановивши другий KDE до згенерованих вами вибірок, і вибравши спостереження, яке максимізує KDE як ваш максимальний a posteriori (MAP).P ( X | Y ) ∝ P ( Y | X ) P ( X ) P ( Y | X ) XP(X)P(X|Y)∝P(Y|X)P(X)P(Y|X)X
Максимальна оцінка ймовірності
... і чому це не працює тут
У моїй оригінальній відповіді методикою, яку я запропонував, було використовувати MCMC для виконання максимальної оцінки ймовірності. Взагалі, MLE - це хороший підхід до пошуку "оптимальних" рішень умовних ймовірностей, але у нас тут є проблема: оскільки ми використовуємо дискримінаційну модель (випадковий ліс в даному випадку), наші ймовірності обчислюються відносно меж рішення . Насправді немає сенсу говорити про "оптимальне" рішення подібної моделі, оскільки, як тільки ми віддаляємось досить далеко від межі класу, модель просто передбачить їх для всього. Якщо у нас достатньо класів, деякі з них можуть бути повністю "оточені", і в цьому випадку це не буде проблемою, але класи на межі наших даних будуть "максимізовані" значеннями, які не обов'язково здійснені.
Щоб продемонструвати, я збираюся використовувати якийсь код зручності, який ви можете знайти тут , який надає GenerativeSampler
класу, який обгортає код з моєї оригінальної відповіді, якийсь додатковий код для цього кращого рішення та деякі додаткові функції, з якими я грав (деякі з яких працюють , які не роблять), на який я, мабуть, не потраплю сюди.
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()
У цій візуалізації х є реальними даними, а клас, який нас цікавить, - зелений. Точки, пов’язані з лінією, - це зразки, які ми намалювали, і їх колір відповідає порядку, в якому вони були відібрані, причому їх “стоншене” положення послідовності задано етикеткою кольорової смуги праворуч.
Як бачимо, пробник відхилився від даних досить швидко, а потім просто зависає досить далеко від значень простору функцій, які відповідають будь-яким реальним спостереженням. Очевидно, це проблема.
Один із способів, який ми можемо обдурити, - це змінити нашу функцію пропозиції, щоб лише функції дозволяли приймати значення, які ми насправді спостерігали в даних. Спробуємо це і подивимось, як це змінює поведінку нашого результату.
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
Очевидним рішенням тут є включення якимось чином для прив’язки нашого процесу вибірки до областей простору функцій, які фактично можуть зайняти дані. Тож давайте замість цього виберемо спільну ймовірність ймовірності, задану моделлю, та числову оцінку для задану KDE, підходити до всього набору даних. Отже, тепер ми ... вибірки з ... ....P ( Y | X ) P ( X ) P ( Y | X ) P ( X )P(X)P(Y|X)P(X)P(Y|X)P(X)
Введіть правило Байєса
Після того, як ти переслідував мене, щоб я мав менше рухатись рукою з математикою тут, я зіграв із цим неабияку суму (отже, я будую GenerativeSampler
річ), і я зіткнувся з проблемами, які я виклав вище. Я почував себе справді, дуже глупо, коли я здійснив це усвідомлення, але очевидно, що ти просиш закликати до застосування правила Байєса, і я прошу вибачення за те, що раніше відхилявся.
Якщо ви не знайомі з правилом Байєса, це виглядає приблизно так:
P(B|A)=P(A|B)P(B)P(A)
У багатьох додатках знаменник - це константа, яка виконує функцію масштабування для того, щоб чисельник інтегрувався до 1, тому правило часто перетворюється таким чином:
P(B|A)∝P(A|B)P(B)
Або простою англійською мовою: "задня частина пропорційна попередньому разу ймовірності".
Вигляд знайомий? Як щодо тепер:
P(X|Y)∝P(Y|X)P(X)
Так, це саме те, про що ми працювали раніше, будуючи оцінку для MLE, прикріпленого до спостережуваного розподілу даних. Я ніколи не замислювався над тим, як Байєс править таким чином, але має сенс, тому дякую, що ви дали мені можливість відкрити цю нову перспективу.
Щоб відслідкувати крихітний шматочок, MCMC - це одне із застосувань правила байєса, де ми можемо ігнорувати знаменник. Коли ми обчислюємо коефіцієнт акцептанта, буде приймати однакове значення і в чисельнику, і в знаменнику, скасовуючи і дозволяючи нам вибирати вибірки з ненормалізованих розподілів ймовірностей.P(Y)
Отже, зробивши це розуміння, що нам потрібно включити пріоритет для даних, давайте зробимо це, встановивши стандартний KDE і подивимось, як це змінює наш результат.
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()
Значно краще! Тепер ми можемо оцінити ваше "оптимальне" значення використовуючи те, що називається "максимальною a posteriori" оцінкою, що є фантастичним способом сказати, що ми підходимо до другого KDE - але для наших зразків цього разу - і знаходимо значення, яке максимальне KDE, тобто значення, що відповідає режиму .P ( X | Y )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()
І ось у вас це є: великий чорний "X" - це наша оцінка MAP (ці контури - це KDE задня частина).