Мои личные заметки из класса машинного обучения. Эти примечания будут и дальше обновляться и улучшаться по мере того, как я продолжаю просматривать курс, чтобы по-настоящему понять его. Большое спасибо Джереми и Рэйчел, которые дали мне возможность учиться.

Уроки: 1234567891011 12

Обзор оптимизации многоуровневых функций с помощью SGD [0:00]

Идея состоит в том, что у нас есть некоторые данные (x), а затем мы что-то делаем с этими данными, например, мы умножаем их на матрицу весов (f (x) ). Затем мы что-то с этим делаем, например, пропускаем через softmax или сигмоид (g (f (x))). Затем мы что-то делаем с этим, например, выполняем потерю перекрестной энтропии или среднеквадратичную потерю ошибки (h (g (f (x)))). Это даст нам некоторый масштабатор. Здесь не будет скрытых слоев. У этого есть линейный слой, нелинейная активация - softmax, а функция потерь - среднеквадратичная ошибка или перекрестная энтропия. Итак, у нас есть исходные данные.

Например [1:16], если нелинейная активация была сигмоидной или softmax, а функция потерь была перекрестной энтропией, тогда это была бы логистическая регрессия. Итак, как нам вычислить производную этого по отношению к нашим весам?

Для этого в основном мы применяем цепное правило:

Следовательно, чтобы получить производную с обратной зависимостью от весов, нам просто нужно вычислить производную по w, используя эту точную формулу [3:29]. Тогда, если бы мы пошли дальше и получили еще один линейный слой с весом w2, теперь нет никакой разницы в вычислении производной по всем параметрам. Мы все еще можем использовать то же самое правило цепочки.

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

Итак, у вас будет список параметров [5:26]. Вот w1, наверное, тензор какого-то более высокого ранга. Если это сверточный слой, это будет тензор 3-го ранга, но мы можем его сгладить. Мы просто составим список параметров. Вот w2. Это просто еще один список параметров. Вот наша потеря - одно число. Следовательно, наша производная - это просто вектор той же длины. Насколько изменение этого значения w влияет на потери? Таким образом, вы можете думать об этом как о функции типа y = ax1 + bx2 + c и сказать, а какова производная от этого по отношению к a, b и c? И у вас будет три числа: производная по a, b и c. Вот и все. Если производная по этому весу, этому весу,…

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

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

Один из хороших способов взглянуть на это - научиться использовать атрибут .grad и метод .backward PyTorch вручную и найти руководства по PyTorch. Таким образом, вы можете начать выполнять некоторые вычисления с векторным вводом и векторным выводом, а затем набрать .backward, а затем набрать grad и посмотреть на него. Затем к некоторым действительно маленьким, имеющим всего 2 или 3 элемента во входных и выходных векторах, сделайте операцию вроде плюс 2 или что-то в этом роде, посмотрите, что это за формы, и убедитесь, что это имеет смысл. Потому что, строго говоря, векторное матричное исчисление не вводит никаких новых концепций во все, что вы изучали в старшей школе. Но чтобы почувствовать, как движутся эти фигуры, потребовалось много практики. Хорошая новость в том, что вам почти никогда не придется об этом беспокоиться.

Обзор наивного байесовского метода и логистической регрессии для НЛП [9:53]

Блокнот / Excel

Мы говорили об использовании такого рода логистической регрессии для НЛП. И прежде чем мы дошли до этого момента, мы говорили об использовании Наивного Байеса для НЛП. И основная идея заключалась в том, что мы могли бы взять документ (например, обзор фильма) и превратить его в набор слов, состоящий из количества повторений каждого слова. Мы называем уникальный список слов словарным запасом. И мы использовали sklearn CountVectorizer для автоматической генерации словаря, который в sklearn они называют «особенности», и создания пакета представлений слов, а вся их группа называется матрицей документа терминов.

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

Затем мы поняли, что, используя правила Байеса и взяв журналы, мы можем в итоге получить что-то, где мы могли бы сложить журналы этих (выделено ниже) плюс журнал отношения вероятностей того, что вещи находятся в классе 1 по сравнению с классом 0. , и в итоге получается что-то, что можно сравнить с нулем [11:32]. Если он больше нуля, мы можем предсказать, что документ положительный, а если он меньше нуля, мы можем предсказать, что документ отрицательный. И это было нашим правилом Байеса.

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

Затем мы поняли, что все в порядке, поэтому, если это не очень хорошая точность (80%), почему бы не улучшить ее, сказав: «Эй, мы знаем другие способы вычислить набор коэффициентов и набор смещений, которые заключаются в их изучении с помощью логистической регрессии. . Другими словами, это формула, которую мы используем для логистической регрессии, и почему бы нам просто не создать логистическую регрессию и не подогнать ее? Это даст нам то же самое, но вместо коэффициентов и смещений, которые теоретически верны на основе этого предположения о независимости и на основе правила Байеса, они будут коэффициентами и смещениями, которые на самом деле являются лучшими в этих данных. Вот где мы и добрались.

Ключевым моментом здесь является практически все: машинное обучение оказывается либо деревом, либо кучей матричных произведений и нелинейностей [13:54]. Кажется, что все сводится к одному и тому же, включая, как оказалось, правило Байеса. Затем выясняется, что какими бы ни были параметры в этой функции, их лучше усвоить, чем рассчитать на основе теории. И действительно, именно это и произошло, когда мы на самом деле попытались узнать эти коэффициенты, у нас получилось 85%.

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

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

Это, в частности, штраф L2. Если бы это было абсолютное значение w, то это было бы штрафом L1. Мы также отметили, что на самом деле нас не волнует функция потерь как таковая, мы рассматриваем только ее производные, которые на самом деле обновляют веса, поэтому, поскольку это сумма, мы можем взять производную каждой части отдельно, и поэтому производная штрафа была равна 2 aw. Итак, мы узнали, что, хотя они математически эквивалентны, у них разные имена. Эта версия (2 aw) называется снижением веса, и этот термин используется в литературе по нейронным сетям.

Перекрестная энтропия [16:34]

Excel

С другой стороны, перекрестная энтропия - это просто еще одна функция потерь, такая как среднеквадратичная ошибка, но она специально разработана для классификации. Вот пример бинарной кросс-энтропии. Скажем, это наш "это кошка или собака?" То есть сказать isCat 1 или 0. И Preds - это наши прогнозы, так что это результат нашего последнего слоя нашей нейронной сети, логистической регрессии и т. Д.

Затем все, что мы делаем, это говорим: `` Хорошо, давайте возьмем фактическое умножение на логарифм прогноза, затем мы добавим к этому 1 минус фактическое умножение на логарифм 1 минус прогноз, а затем возьмем отрицательный результат всего этого.

Я предлагал вам всем попробовать написать версию этого оператора if, так что, надеюсь, вы уже это сделали, иначе я собираюсь испортить это для вас. Итак, это было:

Как нам записать это как оператор if?

if y == 1: return -log(ŷ)
else: return -log(1-ŷ)

Таким образом, ключевое понимание состоит в том, что y имеет две возможности: 1 или 0. Очень часто математика может скрыть ключевое понимание, которое, как мне кажется, происходит здесь, до тех пор, пока вы действительно не подумаете о том, какие значения он может принимать. Вот и все, что он говорит. Либо дайте мне: -log(ŷ) или -log(1-ŷ)

Хорошо, тогда версия с несколькими категориями - это то же самое, но вы говорите, если для большего, чем просто y == 1, но y == 0, 1, 2, 3, 4, 5 . . ., например [19:26]. Таким образом, у этой функции потерь есть особенно простая производная, а также еще одна вещь, с которой вы можете поиграть дома, если хотите, - это подумать о том, как выглядит производная, когда вы добавляете перед ней сигмоид или softmax. Оказывается, вы получите очень хорошие производные.

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

Так что это действительно ключевая мысль [21:16]. Давайте не будем использовать теорию, а будем учиться на данных. И мы надеемся, что добьемся лучших результатов. В частности, мы делаем это с регуляризацией. Тогда я думаю, что ключевая идея регуляризации здесь заключается в том, что давайте не будем пытаться сокращать количество параметров в нашей модели, а вместо этого будем использовать множество параметров, а затем использовать регуляризацию, чтобы выяснить, какие из них действительно полезны.

Больше возможностей с n-граммами [21:41]

"Ноутбук"

Итак, мы пошли дальше, сказав, что если мы можем сделать это с помощью регуляризации, давайте создадим намного больше, добавив биграммы и триграммы. Биграммы, такие как by vast, by vengeance, и триграммы, такие как by vengeance . и by vera miles. Чтобы все работало немного быстрее, мы ограничили его до 800 000 функций, но даже с полными 70 миллионами функций он работает так же хорошо, и не намного медленнее.

veczr =  CountVectorizer(ngram_range=(1,3), tokenizer=tokenize, 
                         max_features=800000)
trn_term_doc = veczr.fit_transform(trn)
val_term_doc = veczr.transform(val)
trn_term_doc.shape
(25000, 800000)
vocab = veczr.get_feature_names()
vocab[200000:200005]
['by vast', 'by vengeance', 'by vengeance .', 'by vera', 'by vera miles']

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

y=trn_y
x=trn_term_doc.sign()
val_x = val_term_doc.sign()
p = x[y==1].sum(0)+1
q = x[y==0].sum(0)+1
r = np.log((p/p.sum())/(q/q.sum()))
b = np.log(len(p)/len(q))

А затем давайте подгоним к этому логистическую регрессию и сделаем несколько прогнозов, и мы получим точность 90%:

m = LogisticRegression(C=0.1, dual=True)
m.fit(x, y);

preds = m.predict(val_x)
(preds.T==val_y).mean()
0.90500000000000003

Так что это выглядит неплохо.

Вернуться к наивному Байесу [22:54]

Вернемся к нашему Наивному Байесу. В нашем Наивном Байесе у нас есть этот термин «матрица документов», а затем для каждой функции мы вычисляем вероятность появления этой функции, если она относится к классу 1, вероятность появления этой функции, если это класс 0, и соотношение этих двух.

И в статье, на которой мы фактически основываем это, они называют p(f|1) p, и они называют p(f|0) q и отношение r.

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

Итак, вы заметите здесь некоторые важные особенности. Вектор r - это вектор ранга 1, и его длина равна количеству объектов. И, конечно же, наша матрица коэффициентов логистической регрессии также имеет ранг 1 и длину, равную количеству функций. И мы говорим, что это два способа расчета одного и того же: один на основе теории, другой на основе данных. Итак, вот некоторые из чисел в r:

r.shape, r
((1, 800000),
 matrix([[-0.05468, -0.161  , -0.24784, ...,  1.09861, -0.69315, -0.69315]]))

Помните, что он использует журнал, поэтому эти числа, которые меньше нуля, представляют вещи, которые с большей вероятностью будут отрицательными, а число больше нуля, скорее всего, будет положительным. Итак, вот е в степени этого (е ^). Итак, есть те, которые мы можем сравнить с единицей, а не с нулем:

np.exp(r)
matrix([[ 0.94678,  0.85129,  0.78049, ...,  3.  ,  0.5 ,  0.5  ]])

Я собираюсь сделать что-нибудь, что, надеюсь, покажется странным [25:13]. Прежде всего, я собираюсь сказать, что мы собираемся делать, а затем я попытаюсь объяснить, почему это странно, а затем мы поговорим о том, почему это может быть не так странно, как мы сначала думали. Итак, вот что мы собираемся делать. Мы возьмем матрицу терминологического документа и умножим ее на r. Это означает, что я могу сделать это здесь, в Excel, мы собираемся сказать, что давайте возьмем все в нашей матрице документа терминов и умножим это на эквивалентное значение в векторе r. Так что это похоже на транслируемое поэлементное умножение, а не на матричное умножение.

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

Итак, мы здесь [26:56]. x_nb (x Наивная байесовская версия) x раз r. А теперь давайте проведем логистическую регрессию с использованием этих независимых переменных. Давайте сделаем это для набора проверки, получим прогнозы и, о чудо, у нас есть лучшее число:

x_nb = x.multiply(r)
m = LogisticRegression(dual=True, C=0.1)
m.fit(x_nb, y);

val_x_nb = val_x.multiply(r)
preds = m.predict(val_x_nb)
(preds.T==val_y).mean()
0.91768000000000005

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

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

Итак, у нас есть наша наивная байесовская версия (x_nb) независимых переменных, и у нас есть некоторый набор весов / коэффициентов (w1), из которых мы обнаружили, что это хороший набор коэффициентов для наших прогнозов. Но x_nb просто равно x раз (поэлементно) r.

Другими словами, это (xnb·w1) равно x*r·w1. Таким образом, мы могли бы просто изменить веса на r·w1 и получить то же число. Таким образом, это должно означать, что изменение, которое мы внесли в независимую переменную, не должно иметь никакого значения, потому что мы можем вычислить точно то же самое, не внося этого изменения. Итак, вот в чем вопрос. Почему это имело значение? Итак, чтобы ответить на этот вопрос, вам нужно подумать о том, что математически не одинаково. Почему он не идентичен? Выдвиньте несколько гипотез, по каким причинам, возможно, мы действительно получили лучший ответ. И чтобы понять это, нам нужно прежде всего начать с того, почему это вообще другой ответ? Это тонко.

Обсуждения [30:33 ~ 32:46]

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

Итак, если ваши веса велики, штраф (aw²) становится больше, и он заглушает часть перекрестной энтропии (x.e.(xw, y)). Но на самом деле это то, что нас волнует. Мы действительно хотим, чтобы он хорошо подходил. Поэтому мы хотим, чтобы регуляризация происходила как можно меньше. Итак, нам нужны меньшие веса (я как бы использую два слова, «меньше» и «меньше», немного эквивалентно, что не совсем справедливо, я согласен, но идея в том, что веса, которые довольно близки к нулю, отчасти не там).

Вот в чем дело [34:38]. Наши ценности r, и я не байесовский болван, но я все равно буду использовать слово предшествующий. Они вроде как априори - мы думаем, что разные уровни важности, а также положительные или отрицательные стороны этих различных функций могут быть чем-то вроде этого. Мы думаем, что плохо больше коррелирует с отрицательным, чем с хорошим. Итак, раньше мы предполагали, что у нас нет априорных значений, другими словами, когда мы говорим о квадрате весов (w²), мы говорим, что ненулевой вес - это то, чего мы не хотим иметь. Но на самом деле я действительно хочу сказать, что я не хочу отклоняться от наивных байесовских ожиданий. Отклоняйтесь от предшествующих наивных байесовских решений, только если у вас нет веских оснований полагать иначе.

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

Вопрос: почему умножать только на r, а не на r² или что-то в этом роде, если на этот раз дисперсия будет намного выше [36:40]? Потому что наш априор основан на реальной теоретической модели. Итак, я сказал, что не люблю полагаться на теорию, но если у меня есть какая-то теория, тогда, возможно, нам следует использовать ее в качестве отправной точки, а не начинать с предположения, что все равно. Итак, наш предшествующий сказал: Эй, у нас есть эта модель, которая называется Наивный Байес, а модель Наивного Байеса сказала, что если предположения Наивного Байеса верны, то r является правильным коэффициентом в этой конкретной формулировке. Вот почему мы выбрали это, потому что наша теория основана на этой теории.

Так что это действительно интересное открытие, которое я никогда не видел освещенным [37:34]. Мысль о том, что мы можем использовать эти традиционные методы машинного обучения, мы можем наполнить их таким байесовским смыслом, начав с включения наших теоретических ожиданий в данные, которые мы даем нашей модели. И когда мы это делаем, это означает, что нам не нужно так сильно регулировать. И это хорошо, потому что мы многое упорядочили ... давай попробуем!

Помните, что способ, которым они это делают в логистической регрессии sklearn, C является обратной величиной штрафа за регуляризацию. Так что мы добавим много регуляризации, сделав его маленьким (1e-5).

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

Базовые линии и биграммы: простая, хорошая тональность и тематическая классификация [39:44]

"Бумага"

Этот метод был впервые представлен в 2012 году. Крис Мэннинг - потрясающий исследователь НЛП из Стэнфорда и Сида Ван, которого я не знаю, но я считаю, что он потрясающий, потому что его статья потрясающая. По сути, они пришли к этой идее. Что они сделали, так это сравнили его с рядом других подходов на ряде других наборов данных. Итак, одна из вещей, которые они пробовали, - это набор данных IMDB. Итак, вот наивная байесовская SVM на биграммах:

Как видите, этот подход превзошел другие линейные подходы, которые они рассматривали, а также некоторые ограниченные подходы на основе нейронных сетей, основанные на машинах Больцмана, которые они рассматривали. В настоящее время есть более эффективные способы сделать это, и на самом деле в курсе глубокого обучения мы показали новый современный результат, который мы только что разработали в Fast AI, который превышает 94%. Но все же особенно для линейной техники, которая является простой, быстрой и интуитивно понятной, это довольно хорошо. И вы заметите, когда они это сделали, они использовали только биграммы. И я предполагаю, что это потому, что я посмотрел на их код, и он был довольно медленным и некрасивым. Как вы видели, я придумал способ еще больше его оптимизировать, и поэтому мы смогли использовать триграммы, так что мы стали намного лучше, и у нас 91,8% против 91,2%, но в остальном он идентичен. Да, еще они использовали машину опорных векторов, которая в данном случае почти идентична логистической регрессии, так что есть некоторые незначительные отличия. Я считаю, что это классный результат, и

Я отмечу, что то, что вы видите здесь, в классе, - это результат многих недель, а часто и месяцев исследований, которые я провожу [41:32]. Поэтому я не хочу, чтобы вы думали, что все это очевидно. Это совсем не так. Как и в этой статье, в ней нет описания того, почему они используют эту модель, чем она отличается, почему они думали, что она работает. Мне потребовалась неделя или две, чтобы даже понять, что это математически эквивалентно нормальной логистической регрессии, а затем еще несколько недель, чтобы понять, что разница на самом деле заключается в регуляризации. Это похоже на машинное обучение, как я уверен, вы заметили из конкурса Kaggle, в котором участвуете. Как будто вы придумываете тысячу хороших идей, 999 из них, независимо от того, насколько вы уверены, что они будут отличными, они всегда оказываются дерьмовыми. Затем, наконец, через четыре недели, один из них наконец-то работает и дает вам энтузиазм, чтобы провести еще четыре недели страданий и разочарований. Это норма. И наверняка все лучшие практики, которых я знаю в области машинного обучения, имеют одну общую черту, а именно то, что они очень-очень стойкие - также известные как упрямые и кровожадные. с еще одной вещью, что все они очень хорошие программисты. Они очень хорошо умеют превращать свои идеи в новый код. Так что это был действительно интересный опыт для меня, прорабатывающего это несколько месяцев назад, чтобы попытаться выяснить, по крайней мере, как объяснить, почему существует этот современный результат.

Еще лучшая версия: NBSVM ++ [43:31]

Итак, как только я это понял, я действительно смог развить это и сделать его немного лучше, и я покажу вам, что я сделал. И именно здесь было очень удобно иметь PyTorch в моем распоряжении, потому что я мог создать что-то, что было настроено именно так, как я хотел, а также очень быстро с помощью графического процессора. Итак, вот какая-то версия NBSVM для Fast AI. На самом деле мой друг Стивен Мерити, замечательный исследователь в области НЛП, окрестил это NBSVM ++, который я считал прекрасным, так что, хотя нет SVM, это логистическая регрессия, но, как я уже сказал, почти то же самое.

Итак, позвольте мне прежде всего показать вам код. Как только я понял, что это лучший способ создать линейную модель набора слов, я встроил ее в Fast AI, чтобы вы могли просто написать пару строк кода.

sl=2000
# Here is how we get a model from a bag of words
md = TextClassifierData.from_bow(trn_term_doc, trn_y, val_term_doc,
                                 val_y, sl)

Итак, код в основном такой: эй, я хочу создать класс данных для классификации текста, я хочу создать его в виде пакета слов (from_bow). Вот мой набор слов (trn_term_doc) и мои ярлыки (trn_y), здесь то же самое для набора проверки и использования до 2000 уникальных слов на обзор, что достаточно.

Итак, затем из этих данных модели создайте обучаемого, который является своего рода обобщением модели Fast AI, основанной на скалярном произведении Наивного Байеса, и затем подгоните эту модель.

learner = md.dotprod_nb_learner()
learner.fit(0.02, 1, wds=1e-6, cycle_len=1)
[ 0.       0.0251   0.12003  0.91552]
learner.fit(0.02, 2, wds=1e-6, cycle_len=1)
[ 0.       0.02014  0.11387  0.92012]                         
[ 1.       0.01275  0.11149  0.92124]
learner.fit(0.02, 2, wds=1e-6, cycle_len=1)
[ 0.       0.01681  0.11089  0.92129]                           
[ 1.       0.00949  0.10951  0.92223]

И через 5 эпох я был уже до 92,2. Таким образом, теперь это значительно превышает линейную базовую линию (в исходной статье). Итак, позвольте мне показать вам код для этого.

Так что код ужасающе короткий. Это оно. И это тоже будет выглядеть, в целом, чрезвычайно знакомым. Здесь есть несколько хитростей: представьте, что эта вещь, которая говорит Embedding, притворитесь, что она действительно говорит Linear. Я сейчас покажу вам вложение. Итак, у нас в основном линейный слой, где количество функций в виде строк, и помните, функции sklearn в основном означают количество слов. Затем для каждого слова мы создадим один вес, который имеет смысл - логистическая регрессия, каждое слово имеет один вес. А затем мы умножим его на значение r, так что для каждого слова у нас будет одно значение r для каждого класса. Так что я на самом деле сделал это так, чтобы он мог обрабатывать не только положительное и отрицательное, но, возможно, выяснить, какой автор создал эту работу - например, может быть пять или шесть авторов.

И в основном мы используем эти линейные слои для получения значения веса и значения r, затем мы берем вес, умноженный на r, и затем суммируем его. Так что это просто скалярное произведение, которое мы сделали бы для любой логистической регрессии, а затем выполняем softmax. Очень незначительная настройка, которую мы добавили, чтобы добиться лучшего результата, это +self.w_adj:

Я добавляю, что это параметр, но я почти всегда использую это значение по умолчанию 0,4. Так что это делает? Это снова меняет предыдущее. Если задуматься, даже после того, как мы использовали это r умноженное на матрицу документа в качестве независимых переменных, вы действительно захотите начать с вопроса, хорошо, штрафные условия все еще опускаются w до нуля. .

Так что же означает, что w равняется нулю? Что бы это значило, если бы у нас были все 0 коэффициентов?

Когда мы умножаем эту матрицу на эти коэффициенты, мы все равно получаем ноль. Таким образом, нулевой вес все равно заканчивается словами: «У меня нет мнения о том, является ли это положительным или отрицательным». С другой стороны, если бы все они были единицами, то это в основном говорит о том, что мое мнение таково, что наивные байесовские коэффициенты в точности верны. Итак, идея состоит в том, что я сказал, что ноль почти наверняка не является правильным приоритетом. На самом деле мы не должны говорить, что если коэффициент отсутствует, это означает игнорирование наивного байесовского коэффициента. 1, вероятно, слишком велико, потому что мы действительно думаем, что наивный байесовский метод - это только часть ответа. Итак, я поигрался с несколькими разными наборами данных, где я в основном сказал: взять веса и добавить к ним некоторую константу. Таким образом, в этом случае ноль станет 0,4. Другими словами, штраф за регуляризацию подталкивает веса не к нулю, а к этому значению. И я обнаружил, что для ряда наборов данных 0.4 работает довольно хорошо и довольно устойчиво. Опять же, основная идея состоит в том, чтобы получить лучшее из обоих миров, где мы учимся на данных, используя простую модель, но мы используем наши предыдущие знания как можно лучше. Итак, оказывается, когда вы говорите, давайте скажем, что весовая матрица нулей на самом деле означает, что вы должны использовать примерно половину значений r, что в конечном итоге работает лучше, чем раньше, когда все веса должны быть равны нулю.

Вопрос: w указывает на объем необходимой регуляризации [50:31]? w - это веса. Итак, x = ((w+self.w_adj)*r/self.r_adj).sum(1) вычисляет наши активации. Мы вычисляем наши активации как равные весам, умноженным на r, их сумму. Это наша обычная линейная функция. Штрафной является моя матрица веса. Вот что наказывается. Поэтому, говоря: "Эй, знаете что", не используйте просто w - используйте w+0.4. 0,4 (т.е. self.w_adj) не наказывается. Это не часть весовой матрицы. Таким образом, весовая матрица получает 0,4 бесплатно.

Вопрос: при этом, даже после регуляризации, каждая функция получает некоторую форму минимального веса [51:50]? Не обязательно потому, что в конечном итоге он может выбрать коэффициент -0.4 для функции и сказать: Знаете что, даже если Наивный Байес говорит, что r должен быть любым для этой функции. Я думаю, тебе следует полностью игнорировать это .

Пара вопросов в перерыве [52:46]. Первый был кратким описанием того, что здесь происходит:

Здесь у нас w плюс времена корректировки веса r:

Итак, обычно мы говорим, что логистическая регрессия - это в основном wx (я проигнорирую предвзятость). Затем мы меняем его на rx·w. Тогда мы сказали, что давайте сначала сделаем x·w бит. Вот эта штука, я на самом деле называю w, что, вероятно, довольно плохо, на самом деле это w раз x:

Поэтому вместо r(x·w) у меня w·x плюс постоянное время r. Итак, ключевая идея здесь заключается в том, что регуляризация требует, чтобы веса были равны нулю, потому что она пытается уменьшить Σ w ². Итак, мы говорим: хорошо, мы хотим приблизить веса к нулю, потому что это наше ожидание отправной точки по умолчанию. Итак, мы хотим оказаться в ситуации, когда, если веса равны нулю, у нас есть модель, которая имеет для нас теоретический или интуитивный смысл. Эта модель (r(x·w)), если веса равны нулю, не имеет для нас интуитивного смысла. Потому что он говорит: "Эй, умножьте все на ноль, чтобы избавиться от всего". На самом деле мы говорим: «Нет, мы действительно думаем, что наша r полезна, и на самом деле хотим сохранить это». Вместо этого возьмем (x·w) и прибавим к нему 0,4. Итак, теперь, если регуляризатор приближает веса к нулю, то он увеличивает значение суммы до 0,4.

Следовательно, это увеличивает значение модели в 0,4 раза r. Другими словами, наша отправная точка по умолчанию, если вы упорядочили все веса вместе, - это сказать: «Да, вы знаете, давайте использовать немного r. Это, наверное, хорошая идея ". Так вот в чем идея. Идея в том, что происходит, когда этот вес равен нулю. И вы хотите, чтобы это было что-то разумное, потому что в противном случае регулирование веса для движения в этом направлении было бы не такой уж хорошей идеей.

Второй вопрос касался n-граммов [56:55]. Итак, N в n-грамме может быть uni, bi, tri, как угодно. 1, 2, 3, сколько угодно граммов. Итак, Этот фильм хорош имеет четыре униграммы: This, movie, is, good. Он имеет три биграммы: This movie, movie is, is good. Он имеет две триграммы: This movie is, movie is good.

Вопрос: Вы не возражаете вернуться к w_adj или 0.4 вещам? Мне было интересно, не повредит ли эта корректировка предсказуемости модели, потому что подумайте о крайнем случае, если это не 0,4, если это 4000 и все коэффициенты будут по существу… [57:45]? Точно. Итак, наши предыдущие потребности должны иметь смысл. Вот почему он называется DotProdNB, так что приоритетным является то, что мы считаем наивным байесовским подходом. Итак, наивный Байес утверждает, что r = p / q - хорошая априорная точка, и мы не только думаем, что она хорошая, но и думаем, что rx + b - хорошая модель. Это наивная байесовская модель. Другими словами, мы ожидаем, что коэффициент 1 будет хорошим коэффициентом, а не 4000. В частности, мы считаем, что ноль, вероятно, не лучший коэффициент. Но мы также думаем, что, возможно, версия Наивного Байеса немного чересчур самоуверенна. Так что, возможно, 1 немного выше. Таким образом, мы почти уверены, что правильное число, если предположить, что модель Наивного Байеса подходит, находится между 0 и 1.

Продолжение вопроса: но я подумал, что пока он не равен нулю, вы толкаете те коэффициенты, которые должны быть равны нулю, до чего-то отличного от нуля, и делаете высокие коэффициенты менее отличительными от нулевых коэффициентов [ 59:24 ]? Ну, видите ли, они не должны быть равны нулю. Они должны быть r. И помните, это внутри нашей прямой функции, поэтому это часть того, что мы берем градиентом. По сути, он говорит: Хорошо, вы все равно можете настроить self.w на все, что захотите. Но просто регуляризатор хочет, чтобы он был равен нулю. Итак, все, что мы говорим, нормально, если вы хотите, чтобы он был равен нулю, тогда я постараюсь сделать так, чтобы ноль давал разумный ответ.

Ничто не говорит о том, что 0.4 идеально подходит для каждого набора данных. Я пробовал несколько разных наборов данных и нашел оптимальные числа от 0,3 до 0,6. Но я никогда не находил ни одного, где 0,4 меньше нуля, что неудивительно. И еще не нашел, где лучше. Идея в том, что это разумное значение по умолчанию, но это еще один параметр, с которым вы можете поиграть, и мне это нравится. Другое дело, что вы можете использовать поиск по сетке или что-то еще, чтобы выяснить для вашего набора данных, что лучше всего. На самом деле, ключевым моментом здесь является то, что каждая предшествующая модель, насколько мне известно, неявно предполагала, что он должен быть равен нулю, потому что у них нет этого параметра. И, кстати, у меня здесь есть второй параметр (r_adj=10), то же самое, что я делаю с r, на самом деле делится на параметр, о котором я не собираюсь сейчас особо беспокоиться, но это другой параметр, который вы можете использовать, чтобы настроить характер регуляризации. В конце концов, я эмпирик, а не теоретик. Я подумал, что это хорошая идея. Почти все мои вещи, которые кажутся хорошей идеей, оказываются глупыми. Этот конкретный результат дал хороший результат на этом наборе данных, а также на нескольких других.

Вопрос: я все еще не понимаю, w + w_adj. Вы упомянули, что мы делаем w + w_adj, чтобы коэффициенты не обнулялись, поэтому мы придаем некоторое значение априорным значениям. Но вы также сказали, что эффект обучения может заключаться в том, что w будет установлено отрицательное значение, которое может сделать w + w_adj равным нулю. Итак, если мы позволяем процессу обучения действительно устанавливать коэффициенты равными нулю, почему это отличается от простого w [1:01:47]? Из-за регуляризации. Потому что мы наказываем его Σ w ². Другими словами, мы говорим, знаете что, если лучше всего игнорировать значение r, это будет стоить вам (Σ w ² ). Вам нужно будет установить w на отрицательное число. Так что делайте это, только если это определенно хорошая идея. Если это явно не хорошая идея, оставьте ее там, где она есть. Это единственная причина. Как и весь этот материал, который мы сделали сегодня, в основном полностью посвящен максимальному увеличению преимущества, которое мы получаем от регуляризации, и утверждению, что регуляризация подталкивает нас к некоторому предположению по умолчанию, и почти вся литература по машинному обучению предполагает, что предположение по умолчанию - все равно нулю. И я говорю, что оказывается, это имеет смысл теоретически и эмпирически выясняется, что на самом деле вы должны решить, каковы ваши предположения по умолчанию, и это даст вам лучшие результаты.

Продолжение вопроса. Было бы правильно сказать, что вы ставите дополнительные препятствия на пути к обнулению всех коэффициентов, и он сможет это сделать, если это действительно стоит это [1:03:30]? Да, точно. Поэтому я бы сказал, что препятствием по умолчанию, если это не сделать коэффициент ненулевым, является препятствием. А теперь я говорю, что нет, препятствие состоит в том, чтобы коэффициент не был равен 0,4 r.

Вопрос: Итак, это сумма w², умноженная на некоторую константу. Если бы константа была, скажем, 0,1, то вес не мог бы стремиться к нулю. Тогда нам может не понадобиться снижение веса [1:04:03]? Если значение константы равно нулю, то регуляризации нет. Но если это значение больше нуля, то есть штраф. И, по-видимому, мы установили ненулевое значение, потому что мы переоснащены. Итак, мы хотим некоторого штрафа. Итак, если есть какое-то наказание, то я утверждаю, что мы должны наказывать вещи, которые отличаются от наших предыдущих, а не то, что мы должны наказывать то, что отличается от нуля. И наша приоритетная задача состоит в том, что все должно быть примерно равно r.

Вложение [1:05:17]

Я хочу поговорить о встраивании. Я сказал притвориться линейным, а мы действительно можем притвориться линейным. Позвольте мне показать вам, насколько мы можем притвориться линейным, как в nn.Linear, создать линейный слой.

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

Таким образом, умножение этой матрицы независимых переменных на эту матрицу коэффициентов даст нам ответ. Итак, вопрос в том, почему Джереми не написал nn.Linear? Почему Джереми написал nn.Embedding? Причина в том, что, если вы помните, на самом деле мы не храним его в таком виде. Потому что это на самом деле шириной 800000 и высотой 25000. Поэтому вместо того, чтобы хранить его вот так, мы храним его так:

Мы храним в этом пакете слов, какие индексы слов. Это редкий способ его хранения. Он просто перечисляет индексы в каждом предложении. Учитывая это, я хочу произвести матричное умножение, которое я вам только что показал, для получения того же результата. Но я хочу сделать это из разреженного представления. Это в основном одна горячая кодировка:

Это что-то вроде фиктивной версии матрицы. Есть ли там слово «это»? Есть ли там слово «фильм»? И так далее. Итак, если мы взяли простую версию, есть ли в ней слово «это» (например, 1, 0, 0, 0, 0, 0), и умножили это на r, то она просто вернется первый пункт:

Таким образом, в общем случае умножение одного вектора на горячую кодировку на матрицу идентично поиску этой матрицы для нахождения в ней n- -ой строки. Это просто говорит о том, что найдите 0-й, первый, второй и пятый коэффициенты:

Это одно и то же. В этом случае у меня есть только один коэффициент для каждой функции, но на самом деле я использовал один коэффициент для каждой функции для каждого класса. Итак, в этом случае классы бывают положительными и отрицательными. Итак, у меня действительно было r положительное (p / q), r отрицательное (q / r):

В двоичном случае, очевидно, излишне иметь и то, и другое. Но что, если бы это было похоже на автора этого текста? Это Джереми, Саванна или Терренс? Теперь у нас есть три категории, нам нужны три значения r. Так что хорошо делать эту разреженную версию, вы можете просто посмотреть 0-ю, первую, вторую и пятую.

Опять же, это математически идентично умножению на одну матрицу с горячим кодированием. Но когда у вас мало входных данных, очевидно, что это намного эффективнее. Таким образом, этот вычислительный трюк, который математически идентичен, а не концептуально аналогичен умножению на одну матрицу горячего кодирования, называется встраиванием. Я уверен, что большинство из вас, вероятно, слышали о встраиваниях, таких как встраивание слов: Word2Vec, GloVe и т. Д. И людям нравится, когда они звучат как эта удивительная новая сложная нейронная сеть. Они не. Встраивание означает ускорение умножения на одну матрицу с горячим кодированием, заменяя ее простым поиском по массиву. Вот почему я сказал, что вы можете думать об этом так, как если бы он сказал self.w = nn.Linear(nf+1, 1):

Потому что на самом деле он делает то же самое. Фактически это матрица с такими размерами. Это линейный слой, но ожидается, что ввод, который мы собираемся дать ему, на самом деле не будет одной матрицей с горячим кодированием, а на самом деле представляет собой список целых чисел - индексы для каждого слова каждого элемента. Итак, вы можете видеть, что функция forward в Fast AI автоматически получает (для более компактного DotProdNB) индексы функций (feature_idx):

Таким образом, они автоматически берутся из разреженной матрицы. Numpy позволяет легко получить эти индексы. Другими словами, у нас есть здесь (feat_idx) список индекса каждого слова из 800 000, содержащихся в этом документе. Итак, здесь (self.w(feat_idx)) говорится, что найдите каждую из тех, что в нашей матрице встраивания, которая содержит 800 000 строк, и верните все, что вы найдете. Это математически идентично умножению на одну матрицу горячего кодирования. Вот и все встраивание. И это означает, что теперь мы можем построить любую модель, например, нейронную сеть любого типа, в которой в качестве входных данных используются категориальные переменные с потенциально очень высокой мощностью. Затем мы можем просто превратить их в числовой код между нулем и количеством уровней, а затем мы можем изучить линейный слой из этого, как если бы у нас был один горячо закодированный, без фактического создания одной горячей закодированной версии и никогда фактически не выполняя эта матрица умножается. Вместо этого мы просто сохраним версию индекса и просто выполним поиск в массиве. Таким образом, градиенты, которые текут обратно, в основном в одной версии с горячим кодированием, все, что было нулевым, не имеет градиента, поэтому градиенты, которые текут обратно, просто обновят конкретную строку матрицы встраивания, которую мы использовали. Так что это фундаментально важно для НЛП, как и здесь, я хотел создать модель PyTorch, которая реализовывала бы это смехотворно простое маленькое уравнение.

Сделать это без этого трюка означало бы, что я загружал массив элементов размером 25 000 на 80 000, что было бы безумием. Так что этот трюк позволил мне написать это. Я просто заменил слово Linear на Embedding, заменил то, что вводит одну горячую кодировку, на что-то, что просто вводит индексы. Вот и все. Затем он продолжал работать, и теперь он тренируется примерно за минуту в эпоху.

Теперь мы можем взять эту идею и применить ее не только к языку, но и ко всему [1:15:30]. Например, прогнозирование продаж товаров в продуктовом магазине.

Вопрос. На самом деле мы ничего не ищем, верно? Мы просто видим тот массив с индексами, который является представлением [1:15:52]? Итак, мы ищем. Представление, которое сохраняется для пакета слов, теперь не 1 1 1 0 0 1, а 0 1 2 5. Итак, тогда нам действительно нужно произвести наше матричное произведение. Но вместо того, чтобы производить матричное произведение, мы ищем нулевое, первое, второе и пятое.

Продолжение вопроса: Значит, мы все еще сохраняем одну матрицу горячего кодирования [1:16:31]? Нет, не знали. Здесь не используется одна матрица с горячим кодированием. Одна матрица горячего кодирования в настоящее время не выделена. В настоящее время мы выделили список индексов и список коэффициентов из весовой матрицы:

Итак, что мы собираемся сделать сейчас, мы собираемся пойти еще дальше и сказать: давайте вообще не будем использовать линейную модель, давайте использовать многослойную нейронную сеть [1:16:58]. И давайте внесем в это потенциально некоторые категориальные переменные. И эти категориальные переменные мы будем иметь просто как числовые индексы. Таким образом, первый слой для них не будет обычным линейным слоем, это будет слой встраивания, который, как мы знаем, математически ведет себя точно так же, как линейный слой. Итак, мы надеемся, что теперь мы сможем использовать это для создания нейронной сети для любых данных.

Конкурс Россманна [1:17:40]

"Ноутбук"

Несколько лет назад на Kaggle проводился конкурс под названием Rossmann, немецкая продуктовая сеть, где они попросили спрогнозировать продажи товаров в своих магазинах. И это включало смесь категориальных и непрерывных переменных. В этой статье Го / Беркхана они описали свою занявшую третье место запись, которая была намного проще, чем занявшая первое место запись, но почти так же хороша, но намного проще, потому что они воспользовались этой идеей того, что они называют встраиванием сущностей. В статье они думали, что это они изобрели это, на самом деле это было написано ранее Йошуа Бенжио и его соавторами в другом конкурсе Kaggle, который предсказывал направления такси. Хотя, я скажу, я чувствую, что Гуо пошел намного дальше в описании того, как это можно использовать во многих других случаях, поэтому мы поговорим и об этом.

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

Начнем с данных. Итак, данные были, магазин №1 на 31 июля 2015 года был открыт. У них было продвижение по службе. Был школьный праздник. Это не был государственный праздник, и было продано 5263 штуки. Это основные данные, которые они предоставили. Таким образом, цель, очевидно, состоит в том, чтобы спрогнозировать продажи в наборе тестов, который содержит информацию без учета продаж. Они также говорят вам, что для каждого магазина это определенный тип, он продает определенный ассортимент товаров, его ближайший конкурент находится на некотором расстоянии, конкурент открылся в сентябре 2008 года, и есть дополнительная информация о промо-акциях, которых я не знаю. подробности того, что это значит. Как и во многих соревнованиях Kaggle, они позволяют вам загружать внешние наборы данных, если вы хотите, если вы делитесь ими с другими конкурентами. Они также рассказали вам, в каком состоянии находится каждый магазин, поэтому люди скачали названия разных штатов Германии, они загрузили файл для каждого штата Германии за каждую неделю, какие-то данные о тенденциях Google. Я не знаю, какая у них тенденция в Google, но она была. Для каждой даты они загрузили кучу информации о температуре. Вот и все.

Одно интересное открытие заключается в том, что Россманн, вероятно, в какой-то мере допустил ошибку, спроектировав это соревнование как соревнование, в котором можно было бы использовать внешние данные [1:21:05]. Потому что на самом деле вы не сможете узнать погоду на следующей неделе или тенденции Google на следующей неделе. Но когда вы соревнуетесь в Kaggle, вас это не волнует. Вы просто хотите выиграть, поэтому используете все, что можете получить.

Очистка данных [1:21:35]

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

Я должен упомянуть, кстати, что я нахожу тот момент, когда вы усердно работаете на соревновании, а затем оно закрывается, и вы не выигрываете. И победитель выходит и говорит, что так я выиграл. Это то место, где вы узнаете больше всего. Иногда это случалось со мной, и это было похоже на то, что я думал об этом, я думал, что попробовал это, а затем я возвращаюсь и понимаю, что у меня там была ошибка, я не тестировал должным образом, и я узнаю о, хорошо, я действительно нужно научиться тестировать эту штуку по-другому. Иногда бывает так: о, я думал об этом, но предполагал, что это не сработает, мне действительно нужно не забыть все проверить, прежде чем делать какие-либо предположения. И знаете, иногда бывает так, будто я не думал об этой технике, вау, теперь я знаю, что она лучше, чем все, что я только что пробовал. Потому что в противном случае, если кто-то скажет: «Эй, ты знаешь, что это действительно хорошая техника, ты вроде в порядке, отлично». Но когда вы месяцами пытались что-то сделать, а кто-то другой делал это лучше, используя эту технику, это довольно убедительно. Так что это довольно сложно, я стою перед вами и говорю, что вот несколько техник, которые я использовал, и я выиграл несколько соревнований Kaggle, и у меня есть некоторые современные результаты. Но к тому моменту, когда она доходит до вас, это уже второстепенная информация. Так что действительно здорово пробовать разные вещи. И также было приятно видеть, в частности, что я заметил в курсе глубокого обучения, довольно много моих студентов, я сказал, что этот метод работает очень хорошо, и они попробовали его, и они вошли в десятку лучших. соревнований Kaggle на следующий день, и они в порядке, это считается очень хорошей работой. Так что соревнования Kaggle полезны по множеству причин. Но один из лучших способов - это то, что происходит после его завершения, и, безусловно, для тех, которые сейчас заканчивают, убедитесь, что вы смотрите форумы, смотрите, чем люди делятся с точки зрения своих решений, и если вы хотите узнать больше о их, не стесняйтесь спрашивать победителей, не могли бы вы рассказать мне больше о том или этом. Обычно люди умеют объяснять. Тогда в идеале попробуйте воспроизвести это самостоятельно. Это может превратиться в отличный пост в блоге или в отличное ядро, чтобы можно было сказать «хорошо», такие-то и такие-то сказали, что использовали эту технику, вот действительно краткое объяснение того, что это за метод, и вот немного кода показывает, как это реализовано, и вот результаты, показывающие, что вы можете получить тот же результат. Это тоже может быть действительно интересным описанием.

Всегда приятно, чтобы ваши данные были максимально простыми для понимания [1:24:58]. Итак, в этом случае данные, полученные от Kaggle, использовали различные целые числа для праздников. Мы можем просто использовать логическое значение, праздник или нет. Так что просто очистите это:

train.StateHoliday = train.StateHoliday!='0'
test.StateHoliday = test.StateHoliday!='0'

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

def join_df(left, right, left_on, right_on=None, suffix='_y'):
    if right_on is None: right_on = left_on
    return left.merge(right, how='left', left_on=left_on,
                      right_on=right_on, suffixes=("", suffix))

Основная причина, по которой я всегда выполняю левое соединение, заключается в том, что после того, как я выполняю соединение, я всегда затем проверяю, есть ли в правой части объекты, которые теперь равны нулю:

store = join_df(store, store_states, "Store")
len(store[store.State.isnull()])

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

Я мог бы пойти дальше и объединить названия штатов с погодой:

weather = join_df(weather, state_names, "file", "StateName")

Если вы посмотрите на таблицу тенденций Google, там есть диапазон за эту неделю, который мне нужно превратить в дату, чтобы присоединиться к нему [1:26:45]:

Хорошая вещь в том, чтобы делать это в Pandas, заключается в том, что Pandas дает нам доступ ко всему Python. Так, например, внутри объекта серии есть атрибут .str, который дает вам доступ ко всем функциям обработки строк. Подобно тому, как .cat дает вам доступ к категориальным функциям, .dt дает вам доступ к функциям даты и времени. Итак, теперь я могу разделить все в этой колонке.

googletrend['Date']=googletrend.week.str.split(' - ',expand=True)[0]
googletrend['State']=googletrend.file.str.split('_', expand=True)[2]
googletrend.loc[googletrend.State=='NI', "State"] = 'HB,NI'

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

И как обычно, давайте добавим метаданные даты к нашим датам [1:27:41]:

add_datepart(weather, "Date", drop=False)
add_datepart(googletrend, "Date", drop=False)
add_datepart(train, "Date", drop=False)
add_datepart(test, "Date", drop=False)

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

trend_de = googletrend[googletrend.file == 'Rossmann_DE']

Итак, у нас будет тенденция Google для этого штата и тенденция Google для всей Германии.

Теперь можно приступить к объединению как обучающей, так и тестовой выборки [1:28:19]. Затем оба проверяют, что у нас нет нулевых значений.

store = join_df(store, store_states, "Store")
len(store[store.State.isnull()])
0
joined = join_df(train, store, "Store")
joined_test = join_df(test, store, "Store")
len(joined[joined.StoreType.isnull()]),len(joined_test[joined_test.StoreType.isnull()])
(0, 0)
joined = join_df(joined, googletrend, ["State","Year", "Week"])
joined_test = join_df(joined_test, googletrend, ["State","Year", "Week"])
len(joined[joined.trend.isnull()]),len(joined_test[joined_test.trend.isnull()])
(0, 0)
joined = joined.merge(trend_de, 'left', ["Year", "Week"], suffixes=('', '_DE'))
joined_test = joined_test.merge(trend_de, 'left', ["Year", "Week"], suffixes=('', '_DE'))
len(joined[joined.trend_DE.isnull()]),len(joined_test[joined_test.trend_DE.isnull()])
(0, 0)
joined = join_df(joined, weather, ["State","Date"])
joined_test = join_df(joined_test, weather, ["State","Date"])
len(joined[joined.Mean_TemperatureC.isnull()]),len(joined_test[joined_test.Mean_TemperatureC.isnull()])
(0, 0)

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

В этом случае мне не нужны были дубликаты, поэтому я просто просмотрел и удалил их:

for df in (joined, joined_test):
    for c in df.columns:
        if c.endswith('_y'):
            if c in df.columns: df.drop(c, inplace=True, axis=1)
for df in (joined,joined_test):
  df['CompetitionOpenSinceYear'] = 
          df.CompetitionOpenSinceYear.fillna(1900).astype(np.int32)
  df['CompetitionOpenSinceMonth'] = 
          df.CompetitionOpenSinceMonth.fillna(1).astype(np.int32)
  df['Promo2SinceYear'] = 
          df.Promo2SinceYear.fillna(1900).astype(np.int32)
  df['Promo2SinceWeek'] = 
          df.Promo2SinceWeek.fillna(1).astype(np.int32)

Главный конкурент этого магазина открыт с некоторой даты [1:28:54]. Итак, мы можем просто использовать Pandas to_datetime, я передаю год, месяц и день. Так что это приведет к ошибке, если у всех не указаны годы и месяцы, поэтому мы собираемся заполнить недостающие числа 1900 и 1 (см. Выше). И что мы действительно хотим знать, так это то, как долго этот магазин был открыт в то время для этой конкретной записи, поэтому мы можем просто вычесть дату:

for df in (joined,joined_test):
  df["CompetitionOpenSince"] = 
          pd.to_datetime(dict(year=df.CompetitionOpenSinceYear,
                        month=df.CompetitionOpenSinceMonth, day=15))
  df["CompetitionDaysOpen"] = 
          df.Date.subtract(df.CompetitionOpenSince).dt.days

Если задуматься, иногда конкуренция начинается позже, чем в этом конкретном ряду, поэтому иногда она будет отрицательной. И, вероятно, нет смысла иметь негативы (то есть он откроется через x дней). Сказав это, я бы никогда не вставил что-то подобное, не запустив сначала модель с этим внутри и без него. Потому что наши предположения о данных очень часто оказывались неверными. В данном случае я не изобретал ни одного из этих этапов предварительной обработки. Я написал весь код, но все это основано на репозитории GitHub, занявшем третье место. Итак, зная, что нужно для того, чтобы занять третье место в конкурсе Kaggle, я почти уверен, что они проверили бы каждый из этих шагов предварительной обработки и убедились, что это действительно улучшило их набор проверок.

for df in (joined,joined_test):
    df.loc[df.CompetitionDaysOpen<0, "CompetitionDaysOpen"] = 0
    df.loc[df.CompetitionOpenSinceYear<1990,"CompetitionDaysOpen"]=0

[1:30:44]

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

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

Занявший третье место в этом конкурсе решил, что количество месяцев, в течение которых конкурс был открыт, они собирались использовать в качестве категориальной переменной. Поэтому, чтобы не было больше категорий, чем нужно, они усекли его до 24 месяцев. Они сказали, что все, что старше 24 месяцев, сокращается до 24. Итак, вот уникальные значения открытых месяцев конкурса, и все числа от нуля до 24. Это означает, что будет матрица вложения, которая будет имеют в основном вектор встраивания для вещей, которые еще не открыты (0), для вещей, которые открыты в течение месяца (1), и так далее.

for df in (joined,joined_test):
    df["CompetitionMonthsOpen"] = df["CompetitionDaysOpen"]//30
    df.loc[df.CompetitionMonthsOpen>24,"CompetitionMonthsOpen"] = 24
joined.CompetitionMonthsOpen.unique()
array([24,  3, 19,  9,  0, 16, 17,  7, 15, 22, 11, 13,  2, 23, 12,  4, 10,  1, 14, 20,  8, 18,  6, 21,  5])

Теперь они абсолютно могли бы сделать это как непрерывную переменную [1:33:14]. У них могло быть просто число здесь, которое представляет собой всего лишь одно число месяцев, в течение которых он был открыт, и они могли бы рассматривать его как непрерывный и вводить его прямо в исходную матрицу веса. Однако то, что я обнаружил, и, очевидно, то, что нашли эти конкуренты, по возможности лучше рассматривать как категориальные переменные. Причина этого в том, что когда вы вводите некоторые вещи через матрицу встраивания, это означает, что каждый уровень можно рассматривать как совершенно по-разному. Так, например, в этом случае, действительно ли что-то было открыто в течение нуля или одного месяца, действительно отличается. Поэтому, если вы введете это как непрерывную переменную, нейронной сети будет сложно найти функциональную форму, которая имеет такую ​​большую разницу. Это возможно, потому что нейронная сеть может все. Но если вы не облегчаете это. Где еще, если вы использовали вложение, рассматривая его как категориальное, тогда у него будет совершенно другой вектор для нуля и единицы. Так что, особенно если у вас достаточно данных, лучше использовать столбцы как категориальную переменную, где это возможно. Когда я говорю, где это возможно, это в основном означает, что количество элементов не слишком велико. Таким образом, если это было похоже на идентификационный номер продажи, который однозначно отличался в каждой строке, вы не можете рассматривать это как категориальную переменную. Поскольку это будет огромная матрица встраивания, и все будет отображаться только один раз или то же самое за километры от ближайшего магазина с точностью до двух знаков после запятой, вы не сделаете это категориальной переменной.

Это своего рода практическое правило, которое они оба использовали в этом соревновании. Фактически, если мы прокрутим вниз до их вариантов, то вот как они это сделали:

Их непрерывными переменными были вещи, которые были действительно непрерывными, например, количество километров до конкурента, температура, конкретное число в тренде Google и т. Д. Где еще, все остальное, в основном, они рассматривали как категоричное.

На сегодня все. Так что в следующий раз мы закончим. Мы увидим, как превратить это в нейронную сеть, и подведем итоги. Тогда увидимся!

Уроки: 1234567891011 12