Обзор проблемы XOR и «проблемы пончика»

Здравствуйте.

Эта лекция предназначена для тех, кто не изучал мой курс по логистической регрессии, но хочет познакомиться с «проблемой пончика» и проблемой XOR. Обе они являются задачами двоичной классификации, при которых нет возможности провести линейный разграничитель, но имеющие решение с использованием линейного классификатора.

Проблема XOR заключается в классификации результатов логического элемента «исключающее ИЛИ», принимающего значение 0, если значения двух входных элементов равны друг другу, и принимающего значение 1, если те имеют разные значения. Как видно на рисунке, невозможно провести прямую, которая бы разделила два класса.

«Проблема пончика» возникает, если данные представлены в виде двух «пончиков» с разными радиусами и одним и тем же центром. Разграничителем в таком случаем, очевидно, является окружность, а не прямая.

В этом курсе вы увидите, что мы можем решить обе эти проблемы при помощи нейронных сетей. Если же вам интересно, каким образом можно решить их без применения нейронных сетей, то предлагаю пройти мой курс по логистической регрессии, находящийся по адресу https://www.udemy.com/datasciencelogisticregressioninpython. Кроме того, этот курс будет полезен, если вы захотите просмотреть темы логарифма функции правдоподобия и градиентного спуска.

Здравствуйте и вновь добро пожаловать на занятия по теме «Обработка данных: глубокое обучение на языке Python, часть 1».

На этой лекции мы снова обратимся к проблеме XOR и «проблеме пончика», с которыми вы впервые познакомились в моём курсе по логистической регрессии. По этой причине код несколько проще, чем в случае собственно нейронных сетей, с которыми мы работали, поскольку это задачи всего лишь двоичной классификации. Соответствующий файл называется xor_donut.py.

Займёмся мы следующим. В этой лекции мы скорее пройдёмся по тексту кода, нежели будем его набирать, а также запустим его. Это связано с тем, что большая его часть повторяется из предыдущих файлов.

import numpy as np

import matplotlib.pyplot as plt

Итак, первое – функция forward. Поскольку у нас лишь двоичная классификация, у нас тут лишь две сигмоиды.

def forward(X, W1, b1, W2, b2):

Z = 1 / (1 + np.exp( -(X.dot(W1) + b1) ))

activation = Z.dot(W2) + b2

Y = 1 / (1 + np.exp(-activation))

return Y, Z

Заметьте, что в функции predict не используется функция argmax.

def predict(X, W1, b1, W2, b2):

Y, _ = forward(X, W1, b1, W2, b2)

return np.round(Y)

Функции производных остались преимущественно прежними.

def derivative_w2(Z, T, Y):

return (T – Y).dot(Z)

def derivative_b2(T, Y):

return (T – Y).sum()

def derivative_w1(X, Z, T, Y, W2):

dZ = np.outer(T-Y, W2) * (1 – Z * Z)

return X.T.dot(dZ)

def derivative_b1(Z, T, Y, W2):

dZ = np.outer(T-Y, W2) * (1 – Z * Z)

return dZ.sum(axis=0)

В качестве функции затрат используется двоичная кросс-энтропийная функция ошибок.

def cost(T, Y):

tot = 0

for n in xrange(len(T)):

if T[n] == 1:

tot += np.log(Y[n])

else:

tot += np.log(1 – Y[n])

return tot

Далее идёт код, посвящённый проблеме XOR. Первые две строки представляют данные. В данном случае у нас нейронная сеть с четырьмя скрытыми единицами. Специальная строка у нас предназначена для отслеживания значения функции правдоподобия. Коэффициент обучения установлен равным 0,0005, штраф регуляризации – равным нулю. Вы можете определить эти параметры, устанавливая разные значения наобум или используя специальные методы вроде перекрёстной проверки.

def test_xor():

X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])

Y = np.array([0, 1, 1, 0])

W1 = np.random.randn(2, 4)

b1 = np.random.randn(4)

W2 = np.random.randn(4)

b2 = np.random.randn(1)

LL = [] # keep track of likelihoods

learning_rate = 0.0005

regularization = 0.

last_error_rate = None

Последующий код посвящён градиентному спуску, и он практически такой же, как и ранее, хотя обратите внимание, что теперь он включает в себя регуляризацию. Кроме того, сюда включён дополнительный код. Так, если текущая частота ошибок не равна предыдущей, то мы выводим на экран частоту ошибок, истинные значения целевых переменных и прогноз. Таким образом, каждый раз, когда что-то меняется, мы выводим на экран это изменение. Кроме того, предусмотрено прерывание цикла, когда логарифм функции правдоподобия начинает наконец расти. После всего это на экран выводится окончательный коэффициент классификации.

for i in xrange(100000):

pY, Z = forward(X, W1, b1, W2, b2)

ll = cost(Y, pY)

prediction = predict(X, W1, b1, W2, b2)

er = np.abs(prediction – Y).mean()

if er != last_error_rate:

last_error_rate = er

print “error rate:”, er

print “true:”, Y

print “pred:”, prediction

if LL and ll < LL[-1]:

print “early exit”

break

LL.append(ll)

W2 += learning_rate * (derivative_w2(Z, Y, pY) – regularization * W2)

b2 += learning_rate * (derivative_b2(Y, pY) – regularization * b2)

W1 += learning_rate * (derivative_w1(X, Z, Y, pY, W2) – regularization * W1)

b1 += learning_rate * (derivative_b1(Z, Y, pY, W2) – regularization * b1)

if i % 1000 == 0:

print ll

print “final classification rate:”, np.mean(prediction == Y)

Теперь рассмотрим функцию test_donut, посвящённый «проблеме пончика» с двумя классами. Для решения данной задачи количество скрытых единиц равно 8, коэффициент обучения равен 0,00005, а штраф регуляризации равен 0,2. Вся последующая часть кода остаётся в точности прежней.

def test_donut():

N = 1000

R_inner = 5

R_outer = 10

R1 = np.random.randn(N/2) + R_inner

theta = 2*np.pi*np.random.random(N/2)

X_inner = np.concatenate([[R1 * np.cos(theta)], [R1 * np.sin(theta)]]).T

R2 = np.random.randn(N/2) + R_outer

theta = 2*np.pi*np.random.random(N/2)

X_outer = np.concatenate([[R2 * np.cos(theta)], [R2 * np.sin(theta)]]).T

X = np.concatenate([ X_inner, X_outer ])

Y = np.array([0]*(N/2) + [1]*(N/2))

n_hidden = 8

W1 = np.random.randn(2, n_hidden)

b1 = np.random.randn(n_hidden)

W2 = np.random.randn(n_hidden)

b2 = np.random.randn(1)

LL = [] # keep track of likelihoods

learning_rate = 0.00005

regularization = 0.2

last_error_rate = None

for i in xrange(160000):

pY, Z = forward(X, W1, b1, W2, b2)

ll = cost(Y, pY)

prediction = predict(X, W1, b1, W2, b2)

er = np.abs(prediction – Y).mean()

LL.append(ll)

W2 += learning_rate * (derivative_w2(Z, Y, pY) – regularization * W2)

b2 += learning_rate * (derivative_b2(Y, pY) – regularization * b2)

W1 += learning_rate * (derivative_w1(X, Z, Y, pY, W2) – regularization * W1)

b1 += learning_rate * (derivative_b1(Z, Y, pY, W2) – regularization * b1)

if i % 100 == 0:

print “ll:”, ll, “classification rate:”, 1 – er

plt.plot(LL)

plt.show()

if __name__ == ‘__main__’:

test_xor()

# test_donut()

Итак, вы можете видеть, как одна и та же нейронная сеть с одним и тем же способом обучения может справляться с разными задачами. Таким образом, вы можете просто запускать и пользоваться одним алгоритмом для обработки разных данных, необходимо лишь разумно подобрать коэффициент обучения, значение штрафа регуляризации и другие гиперпараметры.

Итак, давайте сначала запустим программу для проблемы XOR и посмотрим, что получится. Как вы можете видеть, мы получили 100% точности, причём весьма быстро.

Попробуем теперь справиться с «проблемой пончика» – и не стесняйтесь перемотать вперёд эту часть видео, если вам это не доставляет такое же удовольствие, как мне. В конце концов, мы получили точность 99%.

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: