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

ratings = pd.read_csv(path/'ratings.csv')
ratings.head()

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

Теперь мы можем поместить его в DataFrame, совместная фильтрация станет невероятно простой.

data = CollabDataBunch.from_df(ratings, seed=42)
y_range = [0,5.5]
learn = collab_learner(data, n_factors=50, y_range=y_range)

Это все данные, которые нам нужны. Итак, теперь вы можете пойти дальше и сказать «получить collab_learner», и вы можете передать пакет данных. Вы должны указать, сколько факторов вы хотите использовать (позже мы вернемся к тому, что это значит). Что-то, что может быть полезно, — это сообщить ему, каков диапазон оценок. Посмотрим, как это поможет и после перерыва. Таким образом, в данном случае минимальный балл равен 0, максимальный — 5.

learn.fit_one_cycle(3, 5e-3)

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

Проблема холодного запуска

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

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

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

Таким образом, совместная фильтрация предназначена специально для случаев, когда у вас есть немного информации о ваших пользователях и фильмах, клиентах и ​​продуктах или о чем-то еще.

Более глубокое погружение в совместную фильтрацию

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

Модели, которые Fastai создает для вас, на самом деле являются моделями PyTorch. И модель PyTorch называется nn.Module, это имя в PyTorch их моделей. Здесь немного больше нюансов, но на данный момент это хорошая отправная точка. Когда запускается PyTorch nn.Module (когда вы вычисляете результат этого слоя, нейронной сети и т. д.), в частности, он всегда вызывает для вас метод с именем forward. Так что именно здесь вы можете узнать, как эта штука на самом деле рассчитывается.

Когда модель строится в начале, она вызывает эту вещь под названием __init__.. В Python люди обычно называют это «dunder init». Итак, dunder init — это то, как мы создаем модель, а forward — это то, как мы запускаем модель.

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

Итак, в этом случае модель содержит:

  • набор весов для пользователя
  • набор весов для предмета
  • набор предубеждений для пользователя
  • набор предубеждений для предмета

И каждый из них исходит от этой штуки под названием embedding. Вот определение embedding:

Все, что он делает, это вызывает эту штуку PyTorch под названием nn.Embedding. В PyTorch для вас настроено множество стандартных слоев нейронной сети. Таким образом, он создает вложение. А затем trunc_normal_ просто рандомизирует его. Это то, что создает обычные случайные числа для встраивания.

Встраивание

Неудивительно, что вложение представляет собой матрицу весов. В частности, вложение — это матрица весов, которая выглядит примерно так:

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

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

Подробный код

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

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

Есть одна настройка, которую мы делаем в конце. В нашем случае мы сказали, что существует диапазон y от 0 до 5,5. Итак, вы выполняете скалярное произведение и добавляете два смещения, и это может дать вам любое возможное число вдоль числовой прямой от очень отрицательного до очень положительного числа. Но мы знаем, что всегда хотим получить число от нуля до пяти. Что, если мы сопоставим эту числовую линию вот так с этой функцией. Форма этой функции называется сигмоид. Итак, она будет асимптотой до пяти и асимптотой до нуля.

Таким образом, какое бы число ни получалось из нашего скалярного произведения и добавления смещений, если мы затем проведем его через эту функцию, оно никогда не будет больше 5 и никогда не будет меньше 0. Строго говоря, в этом нет необходимости. Потому что наши параметры могут узнать набор весов, который дает примерно правильное число. Так зачем нам делать эту дополнительную вещь, если в ней нет необходимости? Причина в том, что мы хотим максимально упростить жизнь нашей модели. Если мы на самом деле настроим его таким образом, что он не сможет предсказать слишком много или слишком мало, тогда он сможет потратить большую часть своего веса на предсказание того, что нас волнует, а именно решения, кому какой фильм понравится. Так что это идея, к которой мы будем продолжать возвращаться, когда дело доходит до улучшения работы нейронной сети. Речь идет обо всех этих маленьких решениях, которые мы принимаем, чтобы облегчить сети обучение правильным вещам. Итак, это последняя настройка здесь:

return torch.sigmoid(res) * (self.y_range[1]-self.y_range[0]) + self.y_range[0]

Мы берем результат этого скалярного произведения плюс смещения и проводим через сигмовидную диаграмму. Сигмоид - это просто функция, которая в основном:

но определение не имеет большого значения. Он имеет форму, которую я только что упомянул, и находится между 0 и 1. Если вы затем умножите это на y_range[1] минус y_range[0] плюс y_range[0], то это даст вам что-то между y_range[0] и y_range[1].

Так что это означает, что эта крошечная нейронная сеть, я имею в виду, это толчок, чтобы назвать ее нейронной сетью. Но это нейросеть с одной весовой матрицей и небольшой нелинейностьюy. Сигмоида в конце — это нелинейность, она имеет только один слой весов. Так что это самая скучная нейронная сеть в мире с сигмоидой на конце. На самом деле получается, что производительность близка к ультрасовременной. Я искал в Интернете, чтобы узнать, каковы лучшие результаты, которые люди получают в этой базе данных MovieLens 100k, и результаты, которые я получаю от этой маленькой вещи, лучше, чем любые результаты, которые я могу найти от стандартных коммерческих продуктов, которые вы можете скачайте специализированные для этого. И хитрость, похоже, в том, что добавление этой маленькой сигмоиды имеет большое значение.

Важная терминология

Вот список понятий, которые нам нужно изучить:

  • Входы
  • Веса/параметры (рандомизированные)
  • Активации
  • Функции активации/нелинейности
  • Вывод
  • Потеря
  • Метрика
  • кросс-энтропия
  • Софтмакс
  • Точная настройка (удаление слоев и случайные веса) и (заморозка и разморозка)

Пример нейронной сети распознавания изображений

Давайте подумаем, что происходит, когда вы используете нейронную сеть для распознавания изображений. Возьмем один пиксель. Итак, у вас есть красный, зеленый и синий пиксель. Каждое из них представляет собой некоторое число от 0 до 255 (вы также можете нормализовать их, чтобы они имели среднее значение нуля и стандартное отклонение, равное единице). Итак, красный: 10, зеленый: 20, синий 30. Так что же нам с ними делать? Ну, что мы делаем, так это рассматриваем это как вектор и умножаем на матрицу. Итак, эта матрица (в зависимости от того, как вы думаете о строках и столбцах), давайте рассмотрим матрицу с тремя строками, а затем сколько столбцов? Вы можете выбрать. Как и в случае с совместной фильтрацией, я решил выбрать вектор размера пять для каждого из моих векторов встраивания. Таким образом, это будет означать, что это вложение размера 5. Вы можете выбрать, насколько велика ваша весовая матрица. Так что сделаем размер 5. Это 3 на 5.

Изначально эта весовая матрица содержит случайные числа. Помните, мы только что рассмотрели матрицу весов embedding?

Было две линии; первая строка создавала матрицу, а вторая заполняла ее случайными числами? Это все, что мы делаем. Все это скрыто за кулисами fastai и PyTorch, но это все, что он делает. Таким образом, при настройке создается матрица случайных чисел. Количество строк должно быть 3, чтобы соответствовать вводу, а количество столбцов может быть сколь угодно большим. Поэтому после умножения входного вектора на эту матрицу весов вы получите вектор размера 5.

Люди часто спрашивают: «Насколько мне нужно знать линейную алгебру, чтобы иметь возможность заниматься глубоким обучением?». Это сумма, которая вам нужна. Если вы не знакомы с этим, это нормально. Вам нужно знать о продуктах Matrix. Вам не нужно много знать о них, вам просто нужно знать вычислительно, что они из себя представляют и что они делают. Вам должно быть очень удобно, если матрица размера n, умноженная на матрицу размера m, дает матрицу или размер x (т. е. как совпадают размеры). Итак, если у вас 3, и они помнят в numpy и PyTorch, мы используем @ раз 3 на 5, чтобы получить вектор размера 5.

Затем, что происходит дальше; он проходит через функцию активации, такую ​​как ReLU, которая составляет всего max(0,x), и выдает новый вектор, который, конечно, будет точно такого же размера, потому что функция активации не меняет размер — она только меняет содержимое. Так что это все еще размер 5.

Что произойдет дальше? Умножаем на другую матрицу. Опять же, это может быть любое количество столбцов, но количество строк должно хорошо отображаться. Так что это будет 5 на м. Может быть, у этого есть 5, скажем, на 8. Это даст какой-то результат — он должен быть размером 8, и мы снова пропустим его через ReLU, и снова это даст нам что-то того же размера.

Затем мы можем провести это через другую матрицу. Допустим, мы занимаемся распознаванием цифр. Возможны десять цифр, поэтому моя последняя весовая матрица должна иметь размер 10. Потому что тогда это будет означать, что мой конечный результат — вектор размером 10. Помните, что если вы выполняете распознавание цифр, мы берем наши фактические данные размером 10. И если число, которое мы пытаемся предсказать, было числом 3, то это означает, что на третьей позиции ([0,0,0,1,0,…]) стоит 1.

Итак, что происходит, наша нейронная сеть работает, начиная с нашего ввода и продолжая:

матрица весов→ReLU→ матрица весов→ReLU→ матрица весов→ конечный результат.

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

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

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

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

Активации и параметры относятся к числам. Это числа. Но Параметры — это числа, которые сохраняются, они используются для расчета. Активации – это результат вычислений – рассчитанных чисел. Так что это две ключевые вещи, которые вам нужно запомнить.

Так что используйте эти термины, и используйте их правильно и точно. И если вы читаете эти термины, они означают очень конкретные вещи. Так что не путайте их в голове. И помните, в них нет ничего странного и волшебного — это очень простые вещи.

  • Активация является результатом умножения матриц или функции активации.
  • Параметры — это числа внутри матриц, на которые мы умножаем.

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

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

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

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

На основе этих переписанных заметок. Это помогает напечатать его, а не просто прочитать.