Байєсова нейронна мережа — тепер апельсинова (частина 2)

Як ви думаєте, чого в апельсині більше — шкірки, або, хм, апельсина?



Пропоную, якщо є можливість, піти на кухню, взяти апельсин, очистити і перевірити. Якщо лінь чи немає під рукою — скористаємося нудною математикою: об'єм кулі ми пам'ятаємо зі школи. Нехай, скажімо, товщина шкірки дорівнює від радіуса, тоді ; віднімемо одне з іншого, поділимо обсяг шкірки на обсяг апельсина… виходить, що шкірки близько 16%. Не так вже мало, до речі.

Як щодо апельсина в тысячемерном просторі?

Піти на кухню, на цей раз не вийде; підозрюю, що формулу напам'ять теж не всі знають, але Вікіпедія нам в допомогу. Повторюємо аналогічні обчислення, і з цікавістю виявляємо, що:

  • по-перше, в тысячемерном гиперапельсине шкірки більше, ніж м'якоті
  • а по-друге, її більше приблизно у 246993291800602563115535632700000000000000 разів
Тобто, яким би дивним і суперечливим це не здавалося, але майже весь обсяг гиперапельсина міститься в дуже тонкому шарі прямо під його поверхнею.

Почнемо з цього, мабуть.



Синопсис

Це друга частина поста, де перед цим ми зупинилися на тому, що знайшли головний Грааль — апостериорную ймовірність параметрів моделі. Ось вона, ще раз, на всяк випадок: . Згадаймо ще раз, що — це параметри моделі (ваги нейронної мережі, наприклад), а — дані з датасета (я трохи змінив позначення, раніше замість , але тета нам знадобиться пізніше).

Так от, апельсин — це якраз воно. Розміри цього posterior ростуть з такою ж загрозливою швидкістю, як і обсяг гиперапельсина; чим більше у нас параметрів, тим об'ємнішим» стає розподіл. Насправді, напевно, краще уявити навіть не апельсин — давайте уявимо гору. У тысячемерном вимірі. Так, я знаю, що це трохи непослідовно, але энивей — ось, гора:


всього в одновимірному, правда. За заповітами Хінтона, уявити собі тысячемерную гору можна так: подивитися на картинку вище і голосно сказати «тисяча!» — чи скільки там знадобиться

Наш челендж — це з'ясувати обсяг цієї гори. При цьому ми:
— не знаємо, якої вона форми (може бути якої завгодно)
— поки що володіємо тільки одним способом виміру — стоячи в точці, ми можемо порахувати висоту до підніжжя (ймовірність знаходження в цій точці)
— поверхня гори росте експоненціально, коли зростає — аналогічно тому, як зростає шкірка гиперапельсина

Який план?

перший План: семплінг





Нам, взагалі кажучи, зовсім не обов'язково вимірювати гору прямо у кожній точці. Ми можемо вибрати кілька випадкових ділянок гори, зробити заміри і окреслити за ним результат. Він буде не такий точний, зате нам доведеться зробити набагато менше вимірювань.
Можна досить швидко помітити, що це думка напрошується, але не дуже нам допоможе, коли наша багатовимірна гора почне зростати зі збільшенням числа вимірювань. Давайте згадаємо, що поверхня гори дорівнює <числу значень в одному вимірі> в ступені <числа вимірювань>. Семплінг допоможе нам зменшити підставу ступеня — але показник нікуди не дінеться, і проблема все ще залишається експоненційної.

другий План: апроксимація

Головна проблема вимірювання гори не в тому, що вона велика (в плані кількості вимірювань) — в кінці кінців, обсяг тысячемерного апельсина ми тільки що порахували без особливих труднощів за формулою з Вікіпедії. Проблема в тому, що для гори немає формули. Ми не знаємо аналітичне правило, по якому росте гора (на відміну від апельсина — він росте рівномірно по всіх напрямках).

Ну гаразд, а що якщо ми побудуємо ще одну свою гору, яка буде схожа на шукану, але на цей раз з формулою? Приблизно ось так:



Ну… насправді чорт його знає. По-перше, поки не дуже зрозуміло, настільки точною може бути апроксимація — на картинці вона виглядає не дуже-то. По-друге, ми поки не знаємо, як це зробити. Ось давайте і розберемося.

Апроксимація Лапласа

Ще раз намалюємо нашу теорему Байєса, тільки трохи по-іншому:


Я просто зібрав добуток двох ймовірностей праворуч від дробу в одну — так звану спільну ймовірність (і ще раз нагадаю, що ми трохи змінили на позначення w замість тітки). Люди часто розглядають це в такому сенсі, що — наше «шукане» розподіл, а — нормализующая константа, яка потрібна, щоб результат підсумовувався до одного. Так що давайте тимчасово зосередимося на пошуку .

Говоримо «апроксимація», згадуємо ряди Тейлора. У відповідності з першим курсом матаналізу будь-яка функція розкладається на нескінченну суму опредленных поліномів. Запишемо нашу функцію , а розкладати будемо в якійсь точці , яка співпадає з максимумом розподілу (ми не знаємо, де воно, але це поки не важливо). І заодно отпилим нескінченну суму після доданку зі ступенем два:


(це прямо списане один-в-один розкладання в ряд Тейлора з вікіпедії)

Тепер згадуємо, що ми вибрали як точку максимуму, а отже, похідна в ній дорівнює нулю. Тобто другий доданок можна з чистою совістю викинути. Отримуємо


Нічого не нагадує? Насправді у цієї штуки така ж форма, як у логарифму від гауссиана. Щоб це побачити, можна записати


Трохи змінимо позначення: ми хотіли вибрати , а тепер нехай буде . Тоді і виходить, що наша шукана спільна ймовірність може бути апроксимована гауссианом з центром у точці максимуму і стандартним відхиленням (зворотна друга похідна в точці максимуму або кривизна).

Якщо це було надто багато незрозумілих символів для одного розділу, підсумуємо ідею в трьох коротких пунктах. Отже, якщо ми хочемо знайти наш обсяг гори, нам потрібно:
— знайти точку максимуму
— виміряти в ній кривизну (порахувати другу похідну; тільки увага: похідну за , а не )
— взяти нормальну криву з центром у точці максимуму і стандартним відхиленням — негативно-оберненим до кривизні.

Шукати точку максимуму гори ми вміємо і це набагато легше, ніж вимірювати її всю. Власне, а давайте застосуємо магію до тієї ж байєсівської регресії, з якою ми возилися в минулому пості!

Точку максимуму ми шукали в кінці минулого посту — це центр «байєсівського пучка», «гаряча лінія» і т. д. Я, на всяк випадок, ще раз наведу, як її знайти, але заховаю під спойлер, щоб не лякати людей формулами:

Заголовок спойлера
Тут перша частина під аргмаксом — це likelihood, а друга — гауссів prior. Твір перетворили на суму, взявши логарифм, як зазвичай.

Похідну за можна порахувати аналітично, але оскільки я жахливий ледар, я краще заб'ю все це в пітон і нехай aurograd зробить роботу за мене.

# b і w ми знайшли через градієнтний спуск з регуляризацией, як в кінці минулого посту
# просто запихаем їх в один масив
ext_w = np.hstack([b, w])

# дописуємо до даних одиничку зліва
# це все робиться для того, щоб ext_data.dot(ext_w)
# дорівнювало data.dot(w) + b

ext_data = np.ones((data.shape[0], data.shape[1] + 1))
ext_data[:, 1:] = data

# наша функція log joint, з якої треба брати похідну
# я трохи спростив її і розкрив логарифми
def log_joint(w):
regression_value = ext_data.dot(w)
return (
np.sum(-np.power(target - regression_value, 2.) / (2 * np.power(1., 2.))) +
np.sum(-np.power(w, 2.) / (2 * np.power(1., 2.)))
)

from autograd import elementwise_grad

second_grad_log_joint = elementwise_grad(elementwise_grad(log_joint))

mu = ext_w
sigma = -1. / second_grad_log_joint(ext_w)
cov = np.diag(sigma)

# готово, тепер у нас є розподіл
# some_value = multivariate_normal.pdf(some_point, mu, cov)...



Коли у нас є розподіл, намалювати його — справа техніки; користуємося приблизно тим же способом, що і в минулій частині поста з невеликою модифікацією (сэмплим розподіл в різному видаленні від центру). Отримуємо ось таку симпатичну картинку:



І між іншим, кожна крива тут — поліном 15 ступеня. Стара «брутфорсная» байєсова регресія б давно вже відкинулася полежати на кілька років комп'ютерного часу (там я дещо як витискав п'яту ступінь).

Апроксимація Лапласа всім хороша — швидко, зручно, красиво — але одне погано: вона оцінюється з точки максимуму. Це як судити по всій горі, дивлячись на неї з вершини — при тому, що ми не знаємо, чи реально ми на вершині або застрягли в локальному максимумі, і при тому, що видно звідти далеко не всі (раптом у нас на вершині все дуже плоско і красиво, а під нею-три кілометри суцільного обриву?). Загалом, придумали її для нейронних мереж, як можна здогадатися, не у часи Лапласа, а зовсім навіть в 1991-му, але з тих пір вона світ особливо не завоювала. Так що давайте подивимося на що-небудь більш модне і красиве.

Bayes by backprop: початок

Нарешті ми добралися до того самого методу із заголовної статті DeepMind. Автори назвали його Bayes by backprop — успіху з перекладом на російську мову, ага. Зворотне байесораспространение?

Відправна точка тут така: уявімо, що у нас якась апроксимація (ось де нам знадобився символ тети — це будуть наші параметри аппроксимационного розподілу), і нехай вона буде який-небудь простий форми, теж Гауссових, скажімо. Трюк ось у чому: спробуємо записати вираз який-небудь «дистанції» від нашої апроксимації до оригінальної гори, і мінімізувати цю дистанцію. Оскільки обидві величини — розподілу ймовірностей, заюзаем штуку, яку спеціально придумали для порівняння розподілів: відстань Кульбака-Лейблера. Насправді це тільки звучить трохи страшно, а так це просто ось що:


Якщо уважно подивитись на інтеграл, то стає зрозуміло, що він виглядає як матожидание — під інтегралом якась штука, помножена на , а інтеграл взято за . Тоді можна записати так:


Їдемо далі: в знаменнику у нас коштує , а ми знаємо, що по теоремі Байєса воно дорівнює . Засунемо це у формулу вище і зауважимо, що під матожиданием виявляється — яке не залежить від , і ми можемо його з-під очікування винести:


Це ми зробили дуже круту річ. Тому що якщо ви не забули, вся ця штука дорівнює різниці апроксимації і нашої гори. Керованим параметром тут є , і ми хочемо підлаштувати її так, щоб різниці виявилася мінімальною. Так ось, в процесі цієї мінімізації нам не треба турбуватися про
— тому що воно не залежить від . А це, між іншим, був головний проблемний компонент нашого posterior — щоб його порахувати нам доводилося обійти всі точки гори і скласти їх разом (бо , сума за всіма , і його по-іншому особливо ніяк не отримати).

Однак, їдемо далі. Нас цікавить пошук мінімуму того, що залишилося:


Як ми зазвичай шукаємо мінімуми? Ну, ми беремо похідну і робимо градієнтний спуск. Є ідеї, як взяти похідну від цієї фігні? У мене поки що не дуже.

Bayes by backprop: продовження

Тут настає частина, в яку я так і не зміг інтуїтивно в'їхати, тому доведеться зчепити зуби і стежити за математикою. Частина, яка дозволяє нам взяти похідну, називається reparametrization trick, і складається з наступних кроків:

  1. Припустимо, ми взяли якусь випадкову величину . Ми нічого про неї не знаємо, крім того, що вона обрана так, що . Ось таке у неї властивість, і все поки про неї.
  2. Взагалі кажучи, похідна від матожидания не так страшна, як здається: це лише похідна від інтеграла, тобто, грубо кажучи, від суми, де підсумовування йде по . Саме по собі не страшно, але нудно, тому що ми знову повертаємося до проблеми під назвою «зібрати в купу всі точки нескінченно зростаючого ». Давайте представимо у загальному вигляді, що під матожиданием якась функція , і напишемо нашу похідну:



Нічого особливого ми не зробили — просто розкрили очікування щодо визначення з вікіпедії, записали його знову у вигляді інтеграла. Але стривайте, бачите у кінці? Ми тільки що сказали, що воно одно . І-ііі…


Тепер наш інтеграл робиться за эпсилону, а значить, ми можемо загнати похідну за
під знак інтеграла.


Ось він reparametrization trick: була у нас похідна від матожидания, стало очікування від похідної. Круто? Підозрюю, що якщо ви прямуєте за моїми повільними ведениями пальцем по рядках, то вам не дуже круто, і може бути, навіть не дуже зрозуміло, що змінилося — один фіг нам потрібно інтегрувати , чого ми добилися взагалі?

Суть в тому, що тепер ми можемо апроксимувати цей інтеграл в декількох точках (семплами). А раніше не могли. Раніше похідну потрібно було взяти від всього суми: спочатку зібрати разом купу точок, а потім її диференціювати, і там будь-яка неточність при збиранні могла відвести нас при диференціюванні далеко не в ту сторону. А тепер ми можемо диференціювати в точках, а вже , потім підсумувати результати — і значить, цілком можемо обійтися частковою сумою.

Ось такі суворі порядки в світі наближеного виводу: тут нам потрібні апроксимації, щоб обчислити похідну, навіть не саме posterior-розподіл. Ну нічого, ми майже у мети.

Bayes by backprop: алгоритм

Досі ми ставилися до як до абстрактних «параметрами»: прийшов час їх уточнити. Нехай наша апроксимація гауссових: тоді уявімо у вигляді — середнього значення, і — стандартного відхилення. Ще нам потрібно визначитися з — давайте зробимо його гауссових випадковою величиною з центром в нулі і стандартним відхиленням 1, тобто . Тоді щоб виконувалося умови для репараметризации, нехай


(тут означає поелементне множення). Для мене, правда, тут не дуже очевидно, чому при цьому (якщо хто-небудь зможе на пальцях пояснити в коментах — буду дуже вдячний), але приймемо цей шматочок статті на віру.

Разом наше зворотне байесораспространение складається з наступних кроків:

  1. Починаємо з випадкових для кожного ваги нейронної мережі (або коефіцієнта регресії)
  2. Сэмплим трохи
  3. Одержуємо з нього
  4. Згадуємо нашу функцію дистанції між «справжньою горою» і апроксимацією. Ми одного разу позначали її як , але по-доброму це . Я її нижче все одно напишу як , так простіше (тримаємо в голові, що ).
  5. Вважаємо похідну за . Це буде:


    (звідки плюс? тому що — функція від двох змінних, і обидві залежать від )
  6. Вважаємо похідну за :

  7. Є похідні — справа фігурально в капелюсі. Далі старий добрий градієнтний спуск:



Які-небудь результати

Не знаю як вам, а мені вже набрид цей чорно-жовтий пучок, так що давайте пропустимо цю обов'язкову стадію і зробимо що-небудь більш схоже на нейронну мережу. Автори оригінальної статті отримали симпатичні результати на MNIST-цифрах без всяких додаткових перекручень типу використання згорткових мереж — давайте спробуємо дістатися до них. І мабуть, настав час відкласти симпатичний autograd вбік і озброїтися чим-небудь поважче начебто Theano. Далі буде трохи Theano-специфічного коду, так що якщо ви в ньому не орієнтуєтеся — сміливо прокручуйте.

Дістаємо дані:

from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split
from sklearn import preprocessing

import numpy as np

mnist = fetch_mldata('MNIST original')
N = 5000

data = np.float32(mnist.data[:]) / 255.
idx = np.random.choice(data.shape[0], N)
data = data[idx]
target = np.int32(mnist.target[idx]).reshape(N, 1)

train_idx, test_idx = train_test_split(np.array(range(N)), test_size=0.05)
train_data, test_data = data[train_idx], data[test_idx]
train_target, test_target = target[train_idx], target[test_idx]

train_target = np.float32(preprocessing.OneHotEncoder(sparse=False).fit_transform(train_target))

Оголошуємо параметри. Тут застосуємо невеликий трюк, про який є в статті, але я свідомо не розповідав. Справа ось у чому: для кажого ваги мережі у нас є два параметри — мю і сигма, так? Невелика проблема може виникнути з тим, що сигма повинна завжди бути більше нуля (це стандартне відхилення гауссиана, яке негативним не може бути за визначенням). По-перше, як її ініціалізувати? Ну, можна взяти випадкові числа від чогось дуже близької до нуля (типу 0.0001) до одиниці. По-друге, а не заїде вона в процесі градиетного спуску нижче нуля? Ну, начебто не має, хоча за рахунок всяких арифметичних неточностей після коми може в. загалом, автори запропонували вирішити це елегантно — ми замінимо сигму на логарифм від сігми, і зробимо відповідну поправку в формулу ваг:

(навіщо тут під логарифмом +1? мабуть, з тією ж метою — щоб випадково не взяти логарифм від нуля).

А, ну і раніше ми весь час говорили про вагах як про , але у нас в нейронних мережах часто своя номенклатура — нульовий вага там називається (bias). Не будемо порушувати угоду. Отже:

def init(shape):
return np.asarray(
np.random.normal 0, 0.05, size=shape),
dtype=theano.config.floatX
)

n_input = train_data.shape[1]

# L1
n_hidden_1 = 200
W1_mu = theano.shared(value=init((n_input, n_hidden_1)))
W1_logsigma = theano.shared(value=init((n_input, n_hidden_1)))
b1_mu = theano.shared(value=init((n_hidden_1,)))
b1_logsigma = theano.shared(value=init((n_hidden_1,)))

# L2
n_hidden_2 = 200
W2_mu = theano.shared(value=init((n_hidden_1, n_hidden_2)))
W2_logsigma = theano.shared(value=init((n_hidden_1, n_hidden_2)))
b2_mu = theano.shared(value=init((n_hidden_2,)))
b2_logsigma = theano.shared(value=init((n_hidden_2,)))

# L3
n_output = 10
W3_mu = theano.shared(value=init((n_hidden_2, n_output)))
W3_logsigma = theano.shared(value=init((n_hidden_2, n_output)))
b3_mu = theano.shared(value=init((n_output,)))
b3_logsigma = theano.shared(value=init((n_output,)))

Цю логсигму треба якось уміти запихати в формулу нормального розподілу, тому зробимо під неї свою функцію. Заодно і звичайну оголосимо:

def log_gaussian(x, mu, sigma):
return -0.5 * np.log(2 * np.pi) - T. log(T. abs_(sigma)) - (x - mu) ** 2 / (2 * sigma ** 2)


def log_gaussian_logsigma(x, mu, logsigma):
return -0.5 * np.log(2 * np.pi) - logsigma / 2. - (x - mu) ** 2 / (2. * T. exp(logsigma))

Настав час оцінити наші ймовірності. Робимо ми це, як і збиралися раніше, сэмплингом — тобто крутимося в циклі, на кожній ітерації отримуємо рандомно-ініціалізувалися епсилон, перетворюємо його ваги і збираємо разом. Взагалі для циклів в Theano є scan, але: 1) його явно розробляв колектив маніакальних інквізиторів з метою як можна сильніше зламати мозок користувачеві і 2) для невеликого числа ітерацій нам і звичайний цикл згодиться. Загальна:

n_samples = 10

log_pw, log_qw, log_likelihood = 0., 0., 0.

for _ in xrange(n_samples):

epsilon_w1 = get_random((n_input, n_hidden_1), avg=0., std=sigma_prior)
epsilon_b1 = get_random((n_hidden_1,), avg=0., std=sigma_prior)

W1 = W1_mu + T. log(1. + T. exp(W1_logsigma)) * epsilon_w1
b1 = b1_mu + T. log(1. + T. exp(b1_logsigma)) * epsilon_b1

epsilon_w2 = get_random((n_hidden_1, n_hidden_2), avg=0., std=sigma_prior)
epsilon_b2 = get_random((n_hidden_2,), avg=0., std=sigma_prior)

W2 = W2_mu + T. log(1. + T. exp(W2_logsigma)) * epsilon_w2
b2 = b2_mu + T. log(1. + T. exp(b2_logsigma)) * epsilon_b2

epsilon_w3 = get_random((n_hidden_2, n_output), avg=0., std=sigma_prior)
epsilon_b3 = get_random((n_output,), avg=0., std=sigma_prior)

W3 = W3_mu + T. log(1. + T. exp(W3_logsigma)) * epsilon_w3
b3 = b3_mu + T. log(1. + T. exp(b3_logsigma)) * epsilon_b3

a1 = nonlinearity(T. dot(x, W1) + b1)
a2 = nonlinearity(T. dot(a1, W2) + b2)
h = T. nnet.softmax(nonlinearity(T. dot(a2, W3) + b3))

sample_log_pw, sample_log_qw, sample_log_likelihood = 0., 0., 0.

for W, b, W_mu, W_logsigma, b_mu, b_logsigma in [(W1, b1, W1_mu, W1_logsigma, b1_mu, b1_logsigma),
(W2, b2, W2_mu, W2_logsigma, b2_mu, b2_logsigma),
(W3, b3, W3_mu, W3_logsigma, b3_mu, b3_logsigma)]:

# first weight prior
log_pw += log_gaussian(W, 0., sigma_prior).sum()
log_pw += log_gaussian(b, 0., sigma_prior).sum()

# then approximation
log_qw += log_gaussian_logsigma(W, W_mu, W_logsigma * 2).sum()
log_qw += log_gaussian_logsigma(b, b_mu, b_logsigma * 2).sum()

# then the likelihood
log_likelihood += log_gaussian(y, h, sigma_prior).sum()

log_qw /= n_samples
log_pw /= n_samples
log_likelihood /= n_samples

Фух. Тепер збираємо objective. Десь у цьому місці в пості повинна бути пауза на два тижні, тому що objective, який пропонується в статті (що-то типу
(log_qw - log_pw - log_likelihood / M).sum()
) у мене ніфіга не працював і видавав дуже погані результати. Потім в якийсь момент я зрозумів дочитати статтю до кінця і виявив, що автори радять працювати з минибатчами і усереднювати objective певним чином. Точніше навіть ось таким:

objective = ((1. / n_batches) * (log_qw - log_pw) - log_likelihood).sum() / batch_size

Заодно радили використовувати оптимізатор Adam замість звичайного градієнтного спуску. Ніколи в житті їм не користувався, тому утримаємося від спокуси написати його самостійно і скористаємося готовим.

from lasagne.updates import adam

all_params = [
W1_mu, W1_logsigma, b1_mu, b1_logsigma,
W2_mu, W2_logsigma, b2_mu, b2_logsigma,
W3_mu, W3_logsigma, b3_mu, b3_logsigma
]
updates = adam(objective, all_params, learning_rate=0.001)

Ну а далі все стандартно — train-функція і йдемо безпосередньо навчатися. Весь код можна подивитися тут. Сильно великих відсотків точності там не виходить, правда, але і то хліб.

epoch 0 cost 6.83701634889 Accuracy 0.764
epoch 1 cost -73.3193287832 Accuracy 0.876
epoch 2 cost -89.2973277879 Accuracy 0.9
epoch 3 cost -95.9793596695 Accuracy 0.924
epoch 4 cost -100.416764595 Accuracy 0.924
epoch 5 cost -104.000705026 Accuracy 0.928
epoch 6 cost -107.166556952 Accuracy 0.936
epoch 7 cost -110.469004896 Accuracy 0.928
epoch 8 cost -112.143595876 Accuracy 0.94
epoch 9 cost -113.680839646 Accuracy 0.948

Залишу посилання на статтю, на всяк випадок, ще раз, тому що там багато ще всього цікавого: як перейти від апроксимації гауссианом до чогось більш складного, або наприклад, як використовувати цю штуку в reinforcement learning (ну це ж DeepMind, в кінці кінців).

Q&A

  1. Там в статті і взагалі скрізь часто використовується слово «варіаційний», а в пості про це ніфіга не сказано.

    Тут я, до свого сорому, просто посоромився: у свій час у мене в школі не було варіаційного числення, і я злегка остерігаюся використовувати незнайомі терміни, тим більше що можна і без них обійтися. Але взагалі так: про підхід в цілому можна почитати в розділі варіаційних Байєсівських методів. На пальцях, наскільки я розумію, сенс назви такий: варіаційне числення працює з функціями так само, як звичайний матанализ — з числами. Тобто там, де ми в школі шукали точку, в якій досягався мінімум функції, тут ми шукаємо функцію (ту саму ), яка мінімізує KL-дивергенцію, наприклад.
  2. У минулому пості було питання — а при чому тут dropout і взагалі, чи пов'язаний він якось з усім цим справою?

    Ще як. Дропаут можна розглядати як дешеву версію байесовскости, яка зате дуже проста. Ідея будується все на тій же аналогією з ансамблями, про яку я згадував наприкінці минулого поста: от уявіть, що у вас є нейронна мережа. Тепер уявіть, що ви берете її, випадково відриваєте їй кілька нейронів, і відкладає в сторону. Після ~1000 таких операцій ви отримуєте ансамбль з тисячі мереж, де кожна злегка відрізняється один від одного випадковим чином. Ми усредняем їх передбачення, і отримуємо, що випадкові відхилення місцями компенсують один одного і дають актуальні передбачення. Тепер уявіть, що у вас є байєсова мережа, і ви тисячу разів дістаєте набір її ваги з невизначеності, і отримуєте такий же ансамбль злегка відрізняються один від одного мереж.

    Чим байєсовський підхід крутіше — він дозволяє використовувати цю рандомность контрольованим чином. Ось подивіться ще раз на картинку:



    Кожен вага тут — випадкова величина, але випадковості ці різні. Десь пік гауссианы сильно зрушено вліво, десь праворуч, де він у центрі і має велику дисперсію. Імовірно в результаті навчання кожен ваги набуває таку форму, яка найбільш підходить для виконання мережею свого завдання. Тобто якщо у нас є якийсь дуже важливий вагу, наприклад, який повинен бути строго дорівнює он того-то, інакше вся мережа поламається, то при сэмплинге ваг цей нейрон, швидше за все, залишиться на місці. У випадку з дропаутом ми просто рівномірно-випадковим чином відключаємо ваги, і запросто можемо цей важливий вага грохнути. Дропаут нічого не знає про його «важливість», для нього всі однакові ваги. На практиці це виражається в тому, що димпайндовская мережа дає кращі результати, ніж мережа з дропаутом, хоч і ненабагато.

    Чим дропаут крутіше — це тим, що він дуже простий, звичайно.
  3. Нафіга ви взагалі всім цим займаєтеся? Нейронні мережі, імовірно, повинні бути побудовані за образом і подобою штуковин у нас в голові. Там-то вже явно немає ніяких гауссових ваг, зате є багато всього, що ви, машинлернеры, повністю ігноруєте (часовий фактор, дискретні спайки та інша біологія). Викиньте свої підручники з терверу і покайтеся!

    Хороший спосіб, яким мені подобається думати про нейронні мережі, що включає в себе таку історичну ланцюжок:

    • люди в якийсь момент задумалися, що всяке мислення і взагалі процеси у світі можна організувати по ланцюжках «причина-наслідок». Щось сталося, потім сталося щось ще, і це все разом вплинуло на щось третє.
    • вони почали будувати такі символьні моделі типу звичайних байєсівських мереж (не плутати з сабжем), і зрозуміли, що такі моделі можуть відповідати на всякі різні питання («якщо на вулиці сонячно і Себастьян щасливий, яка ймовірність того, що Себастьяну сьогодні видали зарплату?») і відповідати досить добре.
    • але в такі моделі всі змінні доводилося вбивати руками. У якийсь момент прийшов коннекционизм і сказав: а давайте просто насоздаем купу випадкових змінних і зв'яжемо їх всі один з одним, а потім як-то навчимо цю модель, щоб вона нормально працювала.
    І ось так вийшли у тому числі нейронні мережі. Вони насправді не повинні бути такими, як мокрі біологічні мережі у нас в голові. Вони робляться для того, щоб там десь у себе всередині отримати правильну комбінацію причин і наслідків у вигляді нейронів, які встромляються один в інший. Кожен нейрон всередині — це потенційний «фактор», якась змінна, відповідальна за щось, і байєсова магія допомагає зробити ці фактори більш корисними.

    Що робить кожен нейрон всередині нашої голови? Та чорт його знає. Дуже може бути, що нічого особливого.
  4. Що ще є по цій темі?

    О, багато всього:

    • Варіаційні автоэнкодеры — по-моєму, чи не найпопулярніша модель, і по-хорошому, треба було почати з неї, але вже дуже мені сподобалася ця.

    • насправді якщо зазирнути трохи глибше в історію машинного навчання, то варіаційні апроксимації там стирчать з кожного праски. Скажімо, в deep Bolztmann machine (які особливо ніде не використовуються, здається, але энивей), або в штуці під назвою NADE, яка переробляє машину Больцмана мовою зворотного поширення помилки.
    • Variational black box — я навіть не знаю, що це, тому що натрапив на нього тільки дописуючи пост, але мені вже подобається назва і обіцянку введення.
    • DRAW, до якої я ніяк не доберуся і яка виглядає просто приголомшливо: рекурентна мережа з attention-механізмом, яка може малювати циферки як олівцем.
    • штука під назвою Ladder Network, яка об'єднує supervised і unsupervised learning


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.