Перцептроны

Перцептрон. Понятия

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

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

Если вы проходили какой-либо из моих курсов по глубокому обучению, то сразу поймёте, что алгоритм обучения, используемый для перцептрона, имеет весьма много схожего с обучением нейронных сетей.

Углубимсянемного в историю. Перцептрон был изобретён в 1957 году Розенблаттом.Первоначально он был разработан для распознавания изображений и вызвал бурнуюреакцию в сфере искусственного интеллекта, когда обсуждалась его способностьразговаривать, ходить, видеть, слышать, воспроизводиться и даже обрестисознание. К сожалению, в 1969 году была опубликована книга «Перцептроны», вкоторой демонстрировались ограничения линейных классификаторов. Перцептронизвестен тем, что не способен решить даже проблему XOR,столь часто встречающуюся в курсах по машинному обучению, что привело кзначительному снижению интереса к данной области знаний и началу первой «зимыискусственного интеллекта». В истории глубокого обучения были периодычрезмерного увлечения, которые затем сменялись забвением на долгое время, когдалюди разочаровывались в нём, поскольку оно не соответствовало их ожиданиям. Такчто увлечение искусственным интеллектом – это хорошо, но надо быть реалистом вотношении возможных достижений и не позволять дилетантам экстраполировать еговозможности.

Перейдёмтеперь к теории. Прежде всего рассмотрим ограничения для наших задач.Перцептроны по сути своей обрабатывают только задачи двоичной классификации. Ихотя мы привыкли применять 0 для одного класса и 1 для второго, но в случаеперцептрона более естественно использовать -1 для первого класса и +1 длявторого. Вы поймёте удобство таких обозначений, когда мы перейдём к обучению.

Прогнозированиев перцептроне крайне простое и происходит так же, как и в любом другом линейномклассификаторе: мы просто берём входящее x, умножаем его на весовой коэффициент w и добавляемсвободный член b:

w^T x + b

Какмы знаем, если это выражение в точности равно нулю, то мы попали наразделительную прямую или гиперплоскость; если оно меньше нуля, то выбираетсякласс -1, если больше – то класс +1. Другими словами, функция прогнозированияравна

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

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

w = w + \eta y x.

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

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

Этоповторяется, пока не будет достигнуто максимальное число эпох или не останетсянеправильно классифицированных примеров. η – это то, что называетсякоэффициентом обучения и выбирается небольшим, вроде 0,1 или 0,001:

w = случайный вектор (b = 0)

for epoch in xrange (max_epochs):

    собрать все натекущий момент неправильно классифицированные примеры

    если нет неправильно классифицированныхпримеров —> прерывание

    x, y – случайно выбранный один неправильноклассифицированный пример

    w = w + η*y*x // как правило,η = 1.0, 0.1, 0.001 и т. д.

Возникаетвопрос: как всё это помогает w корректироваться в правильном направлении? Чтобыэто понять, необходимо сначала уяснить геометрию прямых и плоскостей. Напомню,что вектор, перпендикулярный прямой, может определять эту прямую – это называетсянормальным вектором, и это то, что мы обозначаем через w. Свободный членуказывает, где прямая пересекается с вертикальной осью координат, но пока чтобудем его игнорировать. На самом деле свободный член всегда можно включить в w путёмдобавления в xещё одного столбца, состоящего из единиц. После этого можно считать, чтоникакого свободного члена вообще нет.

Итак,сейчас у нас есть некоторое w, указывающее в некотором направлении, пока чтоневерном, – прямая и соответствующее ей w нарисованы чёрным цветом (см. слайд). Здесь женаходится x,пока что неправильно классифицированный – на рисунке он указан красным.Предположим, и wxнаходятся по одну сторону от прямой. Тогда их скалярное произведение большенуля, поскольку угол меньше 90°. Это значит, что оно должен бытьклассифицировано как -1, но ошибочно классифицировано как +1. Ввиду этого мыобновляем w,что эквивалентно вычитанию x из w, поскольку y является целевой переменной (меткой), равной -1. Врезультате прямая сдвигается в направлении, при котором xклассифицируется правильно или, по крайней мере, прямая становится ближе к x, так что вследующий раз при обновлении w, возможно, x будет классифицирован правильно. На данномконкретном рисунке новая прямая, отображенная зелёным, классифицирует x правильно.

Теперьрассмотрим случай, когда x и w находятся по разные стороны от прямой. Вновь-таки,чёрным отображены вектор w и прямая, соответствующие нашему неправильномузначению w.Это значит, что угол между x и w больше 90°, так что их скалярное произведение будетотрицательным. А это, в свою очередь, означает, что мы даём прогноз -1, хотяцелевая переменная равна +1. Обновление теперь эквивалентно прибавлению x к w, поскольку y теперь равно+1. Обратите внимание, как это сдвигает прямую, так чтобы x теперь можнобыло классифицировать правильно, или, во всяком случае, приблизить такуювозможность – в данном случае прямая отображается зелёным, при котором xклассифицируется правильно.

Подводим итог. Мы познакомились с итерационным алгоритмом, способным обучать линейный двоичный классификатор. Мы увидели, как правило обновления «исправляет» w, чтобы оно лучше прогнозировало текущие неправильно классифицированные случаи, а я геометрически показал, как это всё работает.

Перцептрон в коде

Итак, начнём с импорта библиотеки Numpy, а также Matplotlib, чтобы иметь возможность выводить диаграммы. Кроме того, импортируем функцию mnist – мы не будем использовать её в данной лекции, но воспользуемся в следующей. Кроме того, функция datetime, чтобы иметь возможность фиксировать время.

import numpy as np

import matplotlib.pyplot as plt

from util import get_data as get_mnist

from datetime import datetime

Итак, начнём с функции get_data, которая создаёт линейно разделяемые данные. Мы сможем их отобразить, поскольку они двухмерны. У нас будет 300 точек на две размерности с равномерным распределением между -1 и +1.

def get_data():

    w =np.array([-0.5, 0.5])

    b = 0.1

    X =np.random.random((300, 2))*2 – 1

    Y =np.sign(X.dot(w) + b)

    return X, Y

Далееопределяем класс Perceptron и функцию fit.

class Perceptron:

    def fit(self, X, Y, learning_rate=1.0, epochs=1000):

Инициируемслучайным образом созданные весовые коэффициенты.

        D = X.shape[1]

        self.w =np.random.randn(D)

        self.b =0

        N =len(Y)

        costs = []

Цикл,определяющий наличие неправильно классифицированных примеров. Если таковых нет,цикл прерывается.

        forepoch inxrange(epochs):

            Yhat = self.predict(X)

           incorrect = np.nonzero(Y != Yhat)[0]

            iflen(incorrect) == 0:

                break

Иобновление весовых коэффициентов на случайно выбраном неправильноклассифицированном примере.

            i = np.random.choice(incorrect)

           self.w += learning_rate*Y[i]*X[i]

           self.b += learning_rate*Y[i]

            c =len(incorrect) / float(N)

           costs.append(c)

        print“final w:”, self.w, “final b:”, self.b,“epochs:”, (epoch+1), “/”, epochs

       plt.plot(costs)

       plt.show()

Далее определяем функцию predict.

    def predict(self, X):

        returnnp.sign(X.dot(self.w) + self.b)

Функция score.

    def score(self, X, Y):

        P =self.predict(X)

        returnnp.mean(P == Y)

И функция main.

if __name__ == ‘__main__’:

    X, Y =get_data()

   plt.scatter(X[:,0], X[:,1], c=Y,s=100, alpha=0.5)

    plt.show()

    Ntrain =len(Y) / 2

    Xtrain,Ytrain = X[:Ntrain], Y[:Ntrain]

    Xtest, Ytest= X[Ntrain:], Y[Ntrain:]

    model =Perceptron()

    t0 =datetime.now()

   model.fit(Xtrain, Ytrain)

    print“Training time:”, (datetime.now() – t0)

    t0 =datetime.now()

    print“Train accuracy:”, model.score(Xtrain, Ytrain)

    print“Time to compute train accuracy:”, (datetime.now() – t0), “Trainsize:”, len(Ytrain)

    t0 =datetime.now()

    print“Test accuracy:”, model.score(Xtest, Ytest)

    print“Time to compute test accuracy:”, (datetime.now() – t0), “Testsize:”, len(Ytest)

Итак,запустим нашу программу и посмотрим, что получится.

Вначале видим наши данные – они линейно разделяемые. Далее идёт функция затрат. Как видим, она вовсе не уменьшается постоянно, но в целом идёт уменьшение. Мы проделали лишь 550 циклов, но неожиданно получили точность 100% в учебном и проверочном наборах, хотя это и не будет происходить каждый раз.

Перцептрон для MNIST и XOR

Вэтой лекции мы используем ранее созданный перцептрон на данных базы MNIST и проблеме XOR.

Начнёмс определения функции для XOR.

def get_simple_xor():

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

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

    return X, Y

Уберёмстарый код и приступим к базе MNIST. Напоминаю, чтоперцептрон может выполнять только двоичную классификацию, так что нам нужнытолько примеры, где Y равно 0 либо 1.А поскольку перцептрон использует только -1 и +1, то нули преобразовываются в-1.

    X, Y =get_mnist()

    idx =np.logical_or(Y == 0, Y == 1)

    X = X[idx]

    Y = Y[idx]

    Y[Y == 0] =-1

    model =Perceptron()

    t0 =datetime.now()

    model.fit(X,Y, learning_rate=10e-3)

    print“MNIST train accuracy:”, model.score(X, Y)

Далее проблема XOR.

    print“”

    print“XOR results:”

    X, Y =get_simple_xor()

    model.fit(X,Y)

    print“XOR accuracy:”, model.score(X, Y)

Запустимпрограмму.

Итак, мы получили 100% точности на данных MNIST и лишь 50% точности для XOR. Обратите внимание, что мы прошли всю 1000 эпох, но функция затрат для XOR не изменилась. Таким образом, один из недостатков перцептрона состоит в том, что он не «знает», когда не способен справиться с задачей и продолжает работать столько, сколько ему было указано.

Функция потерь перцептрона

В завершение данной темы, мы подробнее рассмотрим процесс обучения перцептрона.

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

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

Функцияпотерь для перцептрона определяется следующим образом:

Такимобразом, одна из функций действует здесь как проверка истинности или ложностиутверждения. Если аргумент истинен, мы получаем 1, если ложен – 0. Другимисловами, функция потерь только увеличивается для любых неправильноклассифицированных примеров. Если же классификация корректна, то вклад вфункцию потерь данного примера равен нулю.

Итак,что происходит, когда мы берём отрицательное значение величины

y_i (W^T x_i)

Напомню,yi принимаетзначения либо +1, либо -1. Для неправильно классифицированного примера этозначит, что данная величина будет иметь противоположный знак, и с учётом знака«-» в общем выражении получится положительное число.

Ещёодно обстоятельство заключается в том, что чем дальше xi от гиперплоскости,определённой w,тем больше потери. Чем это свойство может быть желательным? Представьте, что xi находится оченьблизко от разделяющей гиперплоскости, но всё ещё неправильно классифицируется.Это означает, что угол между xi и w равен почти 90°, что ведёт к тому, что их скалярноепроизведение очень близко к нулю. В результате пример классифицируетсянеправильно, но «не очень неправильно» – небольшое изменение w приводит уже кправильному результату.

Сравнитеэто с ситуацией, когда xi почти параллельно w и при этомнеправильно классифицируется. Это означает, что оно настолько далеко отгиперплоскости, насколько может быть. Тогда угол между xi и w равен 0°.Следовательно, данная гиперплоскость и близко не способна правильно классифицироватьданную точку, и небольшой толчок к изменению w не поможет: точка xi находится в настолько «неправильном»месте, насколько это вообще возможно.

Ранеея упомянул градиентный спуск. Ясно, что если мы возьмём производную по L относительно w, то получимданное выражение:

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

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

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

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

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

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

Share via
Copy link