Обучение нейронной сети

Раздел обучения. Введение и план

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

В этой лекции мы узнаём, о чём пойдёт речь в настоящем разделе.

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

В этом разделе мы сосредоточимся на том, как изменить весовые коэффициенты, чтобы прогнозы стали точными.

 

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

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

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

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

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

Что значат все эти символы и буквы?

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

Во-первых, как вы могли заметить, при обсуждении учебных данных для нейронной сети входные переменные мы обозначаем через X, а целевые переменные – через Y. В общем случае они являются матрицами. X имеет размерность NxD, где N – количество примеров (наблюдений), а D – количество входных признаков. Y иногда считается имеющей размерность Nx1; при работе с матрицами это значит, что Y записывается в виде вектора-столбца. Обратите внимание, что запись Nx1 означает, что мы работаем с двухмерным объектом. В других случаях Y может рассматриваться просто как вектор длины N; тогда это будет одномерный объект. В Numpy он рассматривается именно в таком понимании, поэтому обычно при работе с кодом Y будет одномерным объектом размера N, в отличие от двухмерного объекта размером Nx1.

Неотъемлемой частью нашей работы являются прогнозы. Если X представляет собой входные переменные, а Y – целевые переменные, то p(Y|X) является прогнозом. P(Y|X) представляет собой полное распределение вероятностей по всем индивидуальным значением внутри матрицы Y при заданной матрице X. Это матрица такой же размерности, как и Y.

С другой стороны, p(y=k|X) означает значение вероятности. Оно представляет собой вероятность того, что y принадлежит к классу k при заданном входном векторе x.

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

Запись P(Y|X) весьма неудобна, особенно при написании кода. Как вы знаете, имена переменных не могут содержать круглые скобки, поэтому приходится использовать имена переменных вроде p_y_given_x, py_x или Py, ни одно из которых на самом деле не является оптимальным. Одна из старых этому альтернатив, с которой вы познакомились в курсе линейной регрессии, – использовать для обозначения прогноза символ ŷ, который в коде мы называли переменной Yhat. Заметьте, что и в этом случае нужный символ не получается представить как имя переменной в коде.

Именно из-за таких случаев и происходит путаница. Другой альтернативой может быть использование X для обозначения входных переменных, T – для целевых переменных, Y – для прогнозов. Это удобно в том плане, что исчезает необходимость везде писать p(Y|X). Впрочем, это Y всё равно входит в противоречие с другим Y, поскольку ранее Y обозначало целевую переменную, а теперь – прогноз.

Вот потому-то очень важно знать контекст, в котором используется Y, чтобы понимать, в каком значении оно используется. Если вы одновременно видите Y и T, то должно быть ясно, что T – это целевая переменная, а Y – прогнозная переменная. Если Y присваивается значение исходящей переменной нейронной сети, то должно быть понятно, что Y – прогнозная переменная. Если же вы видите вместе Y и Y_hat или Y и p_y_given_x, то, очевидно, Y в этом случае является целевой переменной.

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

Как вы, вероятно, уже поняли из предыдущих лекций этого курса, у нас есть несколько соглашений о величинах объектов. К примеру, N означает количество собранных нами наблюдений (примеров) эксперимента, D представляет количество входных признаков, являющееся к тому же размером входного слоя нейронной сети, M означает размер скрытого слоя, а K представляет размер исходящего слоя. K является числом исходящих классов и может быть любой величиной от 2 и больше. Обратите внимание, что все они пишутся с большой буквы, но подробнее мы обсудим это позже.

Если у нас нейронная сеть с одним скрытым слоем, одним из способов обозначения весовых коэффициентов является следующий. Матрица весовых коэффициентов, идущих от входного слоя к скрытому, обозначается W, а свободные члены скрытого слоя обозначаются b. Всё это вы уже видели в линейной и логистической регрессиях. В нейронных сетях матрица весовых коэффициентов от скрытого к исходящему слою обозначается V, а свободные члены исходящего слоя обозначаются через c. Не забывайте, что поскольку W, b и c должны отображать вектор от предыдущего слоя к последующему, то размерность W должна быть равной DxM, размерность b должна быть равной M, размерность V – равной MxK, а размерность c – равной K.

Вы можете представить ситуацию, когда при дальнейшем добавлении слоёв в нейронную сеть у нас закончатся буквы, поэтому использование V и c на самом деле может оказаться проблематичным. Вместо этого мы можем перечислить количество наших W и b. Так, при переходе от входного слоя к первому скрытому у нас будут W1 и b1, при переходе от первого скрытого слоя ко второму скрытому слою у нас будут W2 и b2 и так далее. Но поскольку W и b у нас теперь пронумерованы, нам придётся, чтобы не перепутать, присваивать им индексы по-другому. Например, номер слоя можно оставить в качестве индекса, а собственно индексы записывать в круглых или квадратных скобках. Другой вариант – оставить предыдущие подстрочные индексы, а номер слоя записывать надстрочным индексом:

W_1 (i,j), \;or \; W^{(1)}_{_i_j}, \;or \; W_1 [i, j].

Любой из этих способов хорош, если они позволяют не запутаться.

В связи с вышесказанным мы можем указывать или не указывать индексы в разных местах, если они являют собой разные вещи. К примеру, если мы рассматриваем целевую переменную T конкретно для n-го наблюдения и k-го класса, мы можем обозначить её несколькими способами. В Numpy, как мы знаем, всё будет рассматриваться как матрицы, поэтому мы можем записать T[n,k]. В других случаях мы можем также писать T(n,k), Tnk, Tnk и так далее. Главное – помнить, что n и k представляют собой разные вещи: k показывает, какой рассматривается узел исходящего слоя нейронной сети, а n – какое рассматривается наблюдение из наших данных. Поэтому, видимо, лучшим вариантом является использование надстрочных и подстрочных индексов.

Ещё одна связанная с этим проблема – это какие именно буквы мы используем для индексов. Как вы знаете из математики и программирования, i, j и k являются общеупотребимыми при написании оператора цикла или суммирования. Так, в предыдущем примере мы использовали i для индексирования входного слоя, так что оно пробегало значения от 1 до D, j мы использовали для индексирования скрытого слоя, так что оно пробегало значения от 1 до M, а k – для индексирования исходящего слоя, так что оно пробегало значения от 1 до K. I, j и k являются, как вы, вероятно, убедились при изучении программирования, общеупотребимыми, так что неудивительно, что и здесь может быть путаница.

Та же проблема с i, j и k возникает снова, когда у нас заканчиваются буквы – например, когда у нас больше одного скрытого слоя и нам нужно подобрать новые индексы вне этих трёх букв. Самый общий способ – подобрать такие буквы, которые не используются где-либо ещё – например, q, r и s.

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

Далее, у нас есть ещё и коэффициент обучения, который, как вы знаете, используется в градиентном спуске. Обычно для коэффициента обучения мы используем греческие буквы вроде θ или α. В принципе, из контекста должно быть ясно, что мы делаем, поскольку градиентный спуск всегда выступает в одной и той же форме.

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

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

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

Что значит обучить нейронную сеть?

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

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

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

Итак, каковы же основные, уже изученные нами понятия?

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

Следующее, о чём мы поговорим, – о том, как мы определяем нашу функцию затрат. Напомню, что в случае двоичной классификации это в точности то же, как если бы мы подсчитывали правдоподобие выпадения некоторой последовательности при броске монеты. К примеру, у вас выпало два орла и три решки. Тогда общее правдоподобие равно вероятности выпадения орла, умноженное на вероятность выпадения орла, умноженное на вероятность выпадения решки, умноженное на вероятность выпадения решки, умноженное на вероятность выпадения решки. Мы вправе перемножать вероятности, поскольку каждый бросок монеты независим относительно других. Другой способ это записать – обозначить через малое p вероятность выпадения орла: p = p(H). Тогда мы можем сказать, что правдоподобие равно p в степени количества выпавших орлов, умноженное на (1 – p) в степени количества выпавших решек:

Likehood = p^{#H} (1 - p)^{#T}.

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

- {#H log \;p + #T \;log (1-p)}.

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

Мы можем выразить это же в понятиях исходящей вероятности модели логистической регрессии и целевых переменных, которые в двоичном случае могут принимать значение 0 и 1:

cost = J = -\sum_{n=1}^{N} t_n \;log (y_n) + (1 - t_n) log(1 - y_n).

где yn – исходящая переменная логистической регрессии или нейронной сети, tn – целевая переменная (0 или 1).

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

В этом курсе, как вы понимаете, мы выполняем не двоичную, а мультиклассовую классификацию, способную обрабатывать любое количество исходящих переменных. Поэтому лучше будет рассматривать бросок кубика. Существует 6 возможных вариантов броска, но мы обозначим это количество через K. Обозначим через yk вероятность выпадения k-й грани и условимся, что tk равно единице, если выпала k-я грань, и нулю – если она не выпала. Тогда мы можем записать, что правдоподобие для N бросков кубика равно:

likehood = \prod_{{n=1}}^{N} \prod_{k=1}^{K} y ^{t_n_k}_{nk}.

Обратите внимание, что теперь y и t имеют теперь по два индекса каждый, при этом n соответствует номеру рассматриваемого наблюдения, а k соответствует рассматриваемому классу. Отметим также, что для любого конкретного n существует лишь одно tn,k, равное единице, все остальные должны быть равны нулю. Это потому, что если я брошу кубик и выпадет шестёрка, то, очевидно, в этом же броске не может быть другого результата. Другими словами, tn,k представляет собой «матрицу прямого кодирования», или матрицу показателей, состоящую из единиц и нулей.

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

cost= J = - \sum_{n=1}^{N} \sum_{k=1}^{K} t_{n,k} \;log \; y_{n,k}.

Это выражение также называется кросс-энтропийной функцией, но для мультиклассовой классификации.

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

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

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

Share via
Copy link