Практические вопросы машинного обучения. Обзор и итоги

Раздел практических вопросов. Введение и план

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

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

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

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

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

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

Повторное обращение к проблемам XOR и «пончика»

Сейчас мы снова обратимся к проблеме 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%.

Общеупотребимые нелинейные функции и их производные

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

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

\sigma (x) = \frac {1}{1+e{-x}},

\sigma' (x) = \sigma (x) (1-\sigma (x)).

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

И последняя функция, которую мы рассмотрим, – это функция relu (сокращение от rectifier linear unit). Название несколько непонятное, но график этой функции действительно похож на график линейной, да и сама функция действительно является линейной, но лишь в двух различных диапазонах:

y= relu (x) = max (0,x),

\frac{dy}{dx} = 1, \;if \; x>0 \;\;\; 0, \; if \; x>0.

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

Гиперпараметры и перекрёстная проверка

На протяжении курса вы, вероятно, заметили, что некоторые действия кажутся произвольными. К примеру – как выбирается коэффициент обучения? Почему используется 4 скрытых единицы, а не 5, 10 или 1 000? В чём разница между гиперболическим тангенсом и функцией relu в качестве функции активации? Все эти важные элементы именуются гиперпараметрами. Это значит, что они являются параметрами, которые мы должны подобрать, но сами они не являются частью собственно модели. Как же подобрать правильные значения гиперпараметров?

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

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

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

Итак, пункт первый – практика приводит к успеху. Вы должны найти то, что вас интересует, и использовать свои знания. Если вы застряли – задавайте вопросы.

Теперь давайте поговорим о перекрёстной проверке. Это очень простая вещь, но если вы с ней не знакомы, то суть в следующем. Все данные – это сигнал плюс шум. Мы обучаем модель, чтобы она распознавала сигнал, а не шум. Если вы захотите добиться чересчур высокой точности, то вы получите «отличную» точность, но это значит, что модель будет соответствовать и сигналу, и шуму. На рисунке вы можете видеть, чем отличается хорошее соответствие модели (слева) от её переобученности (справа). Именно поэтому мы разделяем данные на учебный и проверочный наборы. Мы хотим отслеживать ошибку в обоих наборах – как с данными, на которых мы обучаем модель, так и на данных, на которых мы её не обучаем. Если вы переобучите модель, вы заметите, что ошибка проверочного набора начнёт расти. Чтобы получить статистический показатель этого процесса, мы измеряем ошибку несколько раз и находим среднее значение и дисперсию. Именно этому и служит перекрёстная проверка.

Суть в том, что вы делите данные на K частей. Предположим, K = 5, тогда у нас будет 5 итераций. При первой итерации мы обучаем модель на частях со 2-й по 5-ю, а проверяем на 1-й части данных. При второй итерации мы обучаем модель на частях 1, 3, 4 и 5, а проверяем модель на части 2. При третьей итерации модель обучается на частях 1, 2, 4 и 5, а проверяется на части 3. Всё это продолжается, K раз. Закончив эту процедуру, мы можем использовать среднее значение коэффициента классификации как меру того, насколько хороша данная конфигурация. Вы также можете использовать статистические тесты вроде  T-test, чтобы сравнить две модели и статистически определить, какая из них лучше, – такая функция есть в библиотеке Scipy. Нет особой необходимости проделывать эту процедуру, но, возможно, вам захочется проверить свою модель таким образом.

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

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

def crossValidation(model, X, Y< K=5)

X, Y = shuffle(X, Y)

sz = len(Y) / K

scores = []

for k in xrange(K):

xtr = np.concatenate([ X[:k*sz, :], X[ (k*sz + sz):, :] ])

ytr = np.concatenate([ Y[:k*sz, :], Y[ (k*sz + sz):, :] ])

xte = X[k*sz: (k*sz + sz), :]

yte = Y[k*sz: (k*sz + sz)]

model.fit(xtr, ytr)

score = model.score(xte, yte)

scores.append(score)

return np.mean(scores), np.std(scores).

Выбор вручную коэффициента обучения и штрафа регуляризации

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

Сначала обсудим коэффициент обучения. Как я недавно указывал, это должна быть малая величина, да и вы познакомились с несколькими примерами. Важным тут является выбор масштаба, на котором вы проверяете различные варианты. Например, если вы устанавливаете значение 0,1, вам нет необходимости пробовать значения 0,9, 0,8, 0,7 и так далее. Лучше всего использовать логарифмический масштаб с разницей на каждом шаге в 10 раз. К примеру, если вы попробовали значение 10-1 и оно оказалось чересчур большим, попробуйте значение, равное 10-2. Если и оно оказалось слишком большим – попробуйте значение 10-3 и так далее. Порой мне приходилось использовать такой малый коэффициент обучения, как 10-7.

Следующий вопрос – как узнать, что коэффициент обучения чересчур велик или чересчур мал?

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

В курсе «Глубокое обучение, часть 2» мы обсудим создание нейронной сети с пакетами учебных данных, когда учебные данные имеют различный размер. В этом случае коэффициент обучения оказывается чувствительным к размеру пакета, поскольку функция затрат является суммой индивидуальных ошибок. Тогда при большом количестве данных функция затрат будет больше. Для её нормализации достаточно поделить её величину на N – количество используемых в данный момент учебных примеров. В таком случае коэффициент обучения может быть подобран независимо от размера пакета.
Подобно тому, как коэффициент обучения может быть выбран независимо от количества данных, штраф регуляризации также можно установить независимо от количества параметров, просто разделив сумму квадратов всех параметров на количество параметров. Заметьте, что я сам не всегда провожу такую нормализацию, но попробовать полезно, если что-то сразу не заработало.
Ранее я столкнулся с одной интересной проблемой – я забыл умножить штраф регуляризации на параметр регуляризации, что автоматически установило параметр, равным 1. Для той конкретной задачи, над которой я работал, такое значение было чересчур большим, так что частота появления ошибки колебалась вокруг случайной точки. Поэтому иногда полезно полностью отключить регуляризацию и сделать коэффициент очень малым, чтобы понять – ваша модель действительно работает, или в код закралась ошибка.

Раздел практических вопросов. Краткие итоги

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

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

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

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

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

Итак, что же дальше?

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

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

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

У библиотеки TensorFlow есть прекрасный онлайн-инструмент для графического отображения обучения нейронной сети, называющийся TensorFlow playground. Мы его также рассмотрим, поскольку, как я указывал на протяжении всего курса, очень важно иметь возможность наглядно видеть обучение нейронной сети. Использование данного инструмента поможет закрепить эту мысль.

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

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

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

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

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

Share via
Copy link