Метод прямого распространения в подробностях, часть 1

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

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

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

Итак, перейдём к постановке задачи.

Как вы знаете, наши учебные данные являются комбинацией входных и целевых переменных. Входные переменные мы обозначаем через X, а целевые – через Y. В данном конкретном случае N = 3, D = 2. Это значит, что в нашем эксперименте мы собрали результаты трёх наблюдений (опытов), и в каждом из них есть два входных признака. Вначале мы рассмотрим двоичную классификацию, а затем рассмотрим мультиклассовую с использованием функции софтмакс.

Наши данные состоят в следующем. Входные признаки таковы: первый признак – имеет ли некоторое лицо техническое образование, второй – сколько часов в день оно тратило на изучение глубокого обучения. Двоичная исходящий признак – достигло ли данное лицо успеха в глубоком обучении. Как мы видим, один из входных признаков – имеет ли лицо техническое образование – является двоичным; установим его значение равным единице, если утверждение истинно, и нулю – если ложно. Мы также видим, что второй признак – сколько часов в день тратилось на изучение глубокого обучения – должен быть положительным вещественным числом. К примеру, вы можете тратить 1,5 часа в день на глубокое обучение, но не можете потратить на него -1 час в день.

В нашем случае лицо №1 технического образования не имеет, тратило по 3,5 часа в день на изучение глубокого обучения и достигло в этом успеха. Лицо №2 имеет техническое образование, тратило по 2 часа в день на глубокое обучение и также достигло успеха. Лицо №3 также имеет техническое образование, но тратило на занятия по глубокому обучению всего по 0,5 часа в день и успеха не достигло. Это и есть наш набор данных:

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

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

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

или

Что замечательно в индексах весовых коэффициентов – так это то, что они показывают, какие узлы они связывают. Так, весовой коэффициент Wij показывает, что с входной стороны он идёт от узла i, а с исходящей – к узлу j. Другими словами, коэффициент Wij соединяет узел i предыдущего слоя с узлом j последующего.

У нас также есть свободные члены; они прибавляются к результату после умножения, поэтому у нас есть три свободных члена. Обозначим их через b. Опять же, b могут быть представлены как три отдельные скалярные величины или в виде вектора размерности 3:

или

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

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

или

И не забывайте, что мы точно так же определить свободный член. Поскольку свободный член добавляется уже после умножения на V, нам достаточно одного свободного члена, так как у нас всего один исходящий узел. Другими словами, это скалярная величина. Обозначим её через c:

Теперь, когда у нас есть все необходимые числовые данные, давайте проведём некоторые вычисления.

Рассмотрим сначала первое наблюдение

и вычислим предсказываемое значение

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

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

Обратите внимание, что каждый член суммы соответствует ребру в нейронной сети так же, как два индекса W соответствуют тому, какой входной и какой исходящий узлы он соединяет. Так, W11 соединяет x1 с z1, W21 – соединяет x2 с z1:

Сделаем теперь то же самое с z2 и z3:

Вот правильные ответы:

Итак, теперь у нас есть все значения z скрытого слоя:

Теперь нам нужно найти Для этого найденные значения необходимо перемножить на V и добавить c, после чего из полученного результата взять сигмоиду:

Как и в случае с весовыми коэффициентами от входных узлов к скрытым, каждое V так же соединяет один узел скрытого слоя с исходящим узлом, так что V1 соединяет z1 с исходящим узлом, V2 соединяет с исходящим узлом z2, а V3 соединяет исходящий узел с z3:

Таким образом, наш прогноз: лицо №1 имеет 70%-й шанс достичь успеха в глубоком обучении.

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

Заметьте, что при использовании записи в векторной и матричной форме мы приходим к виду

Чтобы удостовериться, что всё это работает, подставим числа в данное выражение и получим ожидаемый ответ:

Разумеется, теперь мы хотим сделать то же самое с переходом от скрытого до исходящего слоя. Так и сделаем:

Подставив числа, вы сможете убедиться, что получится ожидаемый ответ 0,70:

Теперь, когда мы разобрались, как работать эффективнее путём векторизации каждой операции, возникает следующий очевидный вопрос: можно ли проделать то же самое сразу для нескольких примеров? Вспомните, что мы рассмотрели лишь один пример, тогда как у нас их три, а отдельно вычислять y1, y2 и y3 – это отнюдь не то, чем мы хотели бы заниматься:

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

Может показаться странным, что в формуле W, X и V поменялись местами. Действительно, почему у нас теперь XW вместо WTX?

Объяснение кроется в том, как именно записывается каждое наблюдение. Когда мы рассматриваем матрицу данных с несколькими наблюдениями и несколькими входными признаками, она записывается в виде матрицы размерности NxD, в которой каждая строка представляет одно наблюдение. Но когда мы рассматриваем это наблюдение как отдельный вектор, оно рассматривается как столбец, поскольку векторы в линейной алгебре рассматриваются именно как векторы-столбцы. Поэтому не забывайте, что в полной матрице данных каждое наблюдение идёт по горизонтали, тогда как если наблюдения рассматриваются по отдельности, они идут вертикально:

,

Чтобы вы твёрдо удостоверились, что всё именно так, давайте рассмотрим нейронную сеть произвольного размера. Пусть при этом количество входных признаков равно D, а количество скрытых единиц равно M. Поскольку каждый вход должен быть соединён с каждым выходом, то размерность W должна быть равной DxM. Если у нас есть N наблюдений, то размерность X будет равна NxD. Не забывайте «золотое правило» перемножения матриц: внутренние размерности должны совпадать, поэтому у нас есть только один способ их перемножить. Если мы умножаем X на W, их внутренние размерности будут равны D, что допускает умножение. Результат будет размерности NxM. Это и понятно, поскольку в скрытом слое мы должны получить M-размерный вектор для каждого из N наблюдений:

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

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

Это приводит к интересному последствию. При использовании софтмакс получается, что наша исходящая переменная будет двухмерной. Но ведь, как вы помните, наш оригинальный набор данных имеет лишь одномерную исходящую переменную! Как же нам совместить такие результаты?

Ответ в том, что исходящие переменные в нейронной сети при использовании софтмакс удобнее выражать в виде матрицы показателей, что, кстати, эквивалентно прямому кодированию (onehot encoding), с которым вы познакомились в моём курсе по линейной регрессии.

Прежде чем мы перейдём к прямому кодированию, давайте рассмотрим представление меток. Метки могут иметь большое значение в реальной жизни. К примеру, ваш набор данных может состоять из изображений БМВ и «ягуаров», а задача классификации заключаться в различии между БМВ и «ягуарами» на изображениях. Как вы уже знаете, при двоичной классификации мы можем закодировать их как 0 и 1; при этом совершенно очевидно, что нет никакой разницы, что будет представлено нулём, а что – единицей. БМВ может быть назначено значение 1, а «ягуару» 0, и наоборот. Это совершенно неважно.

Такое представление удобно, поскольку значение сигмоиды всегда находится в диапазоне между 0 и 1. Результат прогноза зависит от того, к какому значению ближе значение исходящей переменной. Например значение 0,75 значит, что мы должны отнести экземпляр к классу 1, а значение 0,49 – что к классу 0. Но что делать, если у нас более двух меток?

Когда у нас более двух меток, то логично просто продолжить счёт от нуля и единицы – так, если у нас три метки, то целевая переменная может принимать значения 0, 1 и 2. Например, если у нас три метки – БМВ, «ягуар» и «фольксваген», то мы можем пометить их как 0, 1 и 2. Если у нас четыре метки, то целевая переменная принимает значения 0, 1, 2 и 3.

Разница в том, что в отличие от ситуации с двоичной классификацией, мы не округляем результат. Так, мы никогда, получив результат 2,7, не округляем его до трёх, потому что софтмакс так не работает. Не забывайте, функция софтмакс отображает вероятность, а потому её значения всегда находятся в диапазоне от 0 до 1.

Всё дело в том, как мы будем использовать эти целевые переменные. Как именно мы этом будем делать, будет объяснено чуть позже. Пока же достаточно помнить о том, если у нас есть массив A длиной K, то первый элемент массива может быть представлен как A[0], второй элемент – как A[1] и так далее до A[K-1].

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

.

Ясно, что мы имеем 6 различных категорий, поскольку у нас встречаются цифры от 0 до 5, и 8 наблюдений, поскольку размер массива равен 8. Не забывайте, что наименования категорий от 0 до K-1 совершенно условно. Так, 0 может представлять БМВ, 1 – представлять «ягуар», 2 – представлять «фольксваген» и так далее. Очевидно, что мы не можем просто использовать БМВ, «ягуар» и «фольксваген» для индексирования вектора или матрицы, поскольку они являются строками, и именно поэтому мы кодируем их, используя числа от 0 до K-1. Итак, если у нас есть 6 различных категорий, то мы имеем 6 различных исходящих переменных нейронной сети, представленных, как вы можете видеть, шестью столбцами.

Если вы когда-либо пользовались библиотекой SciKit Learn, то, вероятно, помните, что раньше было необходимо кодировать целевые переменные целыми числами от 0 до K-1. Сейчас эта библиотека поддерживает и строки в качестве целевых переменных, но по внутреннему отображению она по-прежнему преобразовывает строки в целые числа 0 до K-1.

Важно иметь в виду, что хоть мы и кодируем целевые переменные с помощью чисел 0 до K-1, но это вовсе не значит, что эти числа как-либо взаимосвязаны друг с другом. Допустим, 0 означает БМВ, 1 означает «ягуар», а 3 означает «фольксваген». Мы знаем, что единица ближе к нулю, чем тройка, но значит ли это, что «ягуар» более похож на БМВ, чем «фольксваген»? Конечно же, нет, ведь мы могли закодировать марки автомобилей и каким-либо другим способом, например, обозначить «фольксваген» единицей, а «ягуар» тройкой. Это лишь числа, представляющие категории, но сами числа никак не взаимосвязаны между собой – это просто различные символы.

Итак, вернёмся к прямому кодированию. У нас есть 8 примеров и 6 классов. Что это значит? Это значит, что пример №1 помечен как категория 0, пример №2 помечен как категория 5, пример №3 помечен как категория 1 и так далее. Мы хотим применить прямое кодирование. Это значит, что нам необходимо поместить данные в матрицу размером 8x6, причём первоначальные целевые переменные показывают нам, куда подставить единицу в матрице показателей.

Поскольку первая строка представляет категорию 0, то нулевой элемент первой строки матрицы показателей должен быть равен 1. Поскольку вторая строка представляет категорию 5, то пятый элемент второй строки матрицы показателей должен быть равен 1. Не забывайте, что матрица показателей содержит лишь нули и единицы. Другой способ кратко записать это правило:

Показатель(n, k) = 1, если Y(n) = k, иначе 0.

Чтобы помочь вам усвоить эту концепцию, давайте напишем псевдокод для преобразования методом прямого кодирования одномерного массива целевых переменных, пронумерованных от 0 до K-1, в матрицу показателей. Чуть позже мы напишем настоящий код, поэтому стоит ознакомиться с его сутью уже сейчас.

В качестве входных данных мы берём одномерный массив Yin, содержащий метки от 0 до K-1. Мы также должны ввести N – количество примеров, являющееся длиной массива Yin, а также K, равного максимальному значению Yin. Далее мы инициируем матрицу нулей размерности NxK, а затем для каждой целевой переменной Yin устанавливаем единицу в соответствующей строке исходящей матрицы Yout.

def convert_numbered_targets_to_indicator_matrix(Yin):

N = length(Yin)

K = max(Yin) + 1

Yout = zeros (N, K)

for n in xrange(N):

Yout[n, Yin[n]] = 1

return Yout

Теперь должно быть понятно, почему значения Yin должны быть от 0 до K-1 – потому что они являются индексами в матрице показателей Yout, вторая размерность которой равна K.

У вас может возникнуть вопрос – а зачем преобразовывать целевые переменные в матрицу показателей? Не забывайте, что исходящим результатом работы нейронной сети является список вероятностей – вероятностей того, что входная переменная принадлежит каждому из классов. Но не забывайте, что вероятности служат нам для изучения неизвестных данных. А что с известными? В таком случае вероятность должна быть равной единице или нулю, поскольку в этом случае нет никакой неопределённости. К примеру, какова вероятность того, что вы смотрите видео по глубокому обучению? Она равна единице, поскольку это то, что уже произошло. Соответственно, вероятность того, что вы смотрите видео по глубокому обучению равно единице, а вероятность того, что не смотрите, – нулю.

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

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

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