Линейная регрессия с нуля с использованием Pytorch и Autograd

Фреймворки нейронных сетей, автоматические решения и основные числовые библиотеки, такие как Scikit-learn и SciPy, отвлекли большую часть логики и математики от реализации алгоритмов рабочих лошадок. В эту категорию попадают линейные регрессии. Регрессии - это фундаментальные методы, которые часто столь же эффективны, как и более сложные модели, но мы иногда недооцениваем их, просто «проводя черту через данные». Это чрезмерное упрощение приводит к тому, что мы забываем, что самая базовая нейронная сеть на самом деле представляет собой линейную регрессию без скрытых слоев, а это означает, что понимание линейных регрессий является ключом к пониманию методов глубокого обучения. Следовательно, более глубокое понимание этих моделей может обеспечить лучшее понимание того, когда, где и как использовать эти методы, и какие (если есть) допущения, сделанные с помощью моделей, влияют на их бизнес и производственное использование.

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

Векторы, тензоры и линейные уравнения

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

Нормальное уравнение

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

Это уравнение найдет значение θ для функций X и целевого y, которое минимизирует функцию линейной среднеквадратичной ошибки для этого уравнения, как показано ниже:

Таким образом, если у нас есть какие-то гиперспецифические, основанные на ограничениях, данные о популярных пандемических развлечениях, такие как информация о времени расстойки, кислотности и возрасте закваски, мы можем точно рассчитать матрицу весов, которая будет предсказывать целевой вкус и хрустящая корочка полученного хлеба. Примечание: на данный момент автор испек (и съел) больше буханок, чем вел бесед с марта 2020 года.

Используя обычное уравнение, мы сначала вычисляем скалярное произведение транспонированной X (XT) и самого себя (XTX), а затем находим обратную матрицу. , (XTX) ^ - 1. Впоследствии мы найдем скалярное произведение этой инвертированной матрицы и транспонированное XT снова (XTX) ^ - 1 * XT, прежде чем найдем окончательное скалярное произведение результирующей матрицы из этот расчет с целевыми функциями y, (XTX) ^ - 1 * XT y. Результатом этого расчета будет матрица весов, которую мы можем использовать для прогнозирования целевых характеристик для новых партий закваски. Здесь есть важное предостережение: они представляют собой веса, которые возвращают минимальные потери. Это не означает, что они идеально предсказывают каждую новую цель, просто линейное уравнение, описываемое этими значениями, соответствует данным настолько хорошо, насколько это возможно математически. Другими словами, это глобальные минимумы. Ниже приведен один линейный расчет для поиска решения нормального уравнения с использованием Numpy linalg, T и точечных методов. В приведенном ниже примере я использовал вышеупомянутые данные по закваске, о которых я расскажу позже.

Итак, если у нас есть уравнение для поиска наилучших возможных значений для любой линейной регрессии, зачем нам решать его с помощью какой-либо другой методологии? Нормальное уравнение хорошо работает с небольшими данными, его можно даже вычислить вручную для достаточно малых наборов, но оно плохо масштабируется с размером объекта. Хотя умножение матриц является вычислительно простым, этап обращения матрицы неэффективен и находится где-то между O (n²) -O (n³). Таким образом, в худшем случае удвоение функций обойдется в восемь раз дороже с точки зрения вычислений, а это означает, что с сотнями тысяч функций эта операция действительно начинает давать сбои ». Следовательно, для моделирования более сложных линейных регрессий нам нужен другой инструмент; нам нужен градиентный спуск.

Автоматический расчет градиента

Мы собираемся начать с очень простого примера, в котором мы также можем представить Autograd и его функции автодифференциации. Первое, что мы собираемся сделать, это создать экземпляры некоторых тензоров со значениями для решения линейного уравнения, такого как Y = W * X + B: где значения X, W и B даны как 1, 2 и 3 соответственно.

Теперь мы создали переменные W, X и B и объявили requires_grad=True для X и W. Это создает вычислительный граф для W и X, который отслеживает историю операций для этих переменных и выполняет автоматические вычисления градиента. Автодифференциация будет важна позже, поскольку быстрые вычисления градиента значительно ускорят оптимизацию градиентного спуска. Ниже приведен пример вычислительного графа для умножения двух переменных x и y. Вы можете видеть, что градиент переменной x автоматически вычисляется как 2 при вызове обратного метода из выходной переменной z.

Чтобы решить приведенное выше линейное уравнение для нашего Y, все, что нам нужно сделать, это умножить наши характеристики (X) на наши веса (W), а затем добавить смещение (B). Это дает нам значение Y, равное 5. Теперь мы можем проиллюстрировать, для чего мы используем возможности автодифференциации PyTorch / Autograd. Учитывая, что мы знаем, что произведение этих тензоров равно 5, мы знаем, что произведение W или X и его градиента, наклон уравнения в этой точке плюс точка пересечения B даст нам 5. Мы можем доказать это ниже.

Чтобы проверить: x = 1, его градиент равен 2, поэтому X * X.grad + B = 5. Имейте это в виду, это пригодится позже, когда мы будем использовать эту функцию для минимизации функции потерь для схождения другой модели линейной регрессии. . Здесь стоит подчеркнуть, что уравнение линейной регрессии может быть расширено за пределы отдельных переменных и, как было сказано ранее, часто вычисляется для векторов и матриц. На приведенной ниже диаграмме показано то же линейное уравнение Y = W * X + B для матриц вместо одноточечных значений.

Данные линейной регрессии

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

Я использовал numpy для создания массивов ниже, поскольку этот api, вероятно, наиболее знаком людям. Затем я конвертирую их в тензорные тензоры, чтобы показать, насколько легко вывести вещи в среду Pytorch. Обратите внимание: это можно было сделать с самого начала, создав входы и цели с помощью torch.tensor ().

Поскольку эти данные очень малы, нормальное уравнение было бы подходящим, однако мы будем строить модель градиентного спуска, чтобы проиллюстрировать, как решить с помощью этого метода для больших наборов данных. В идеальной ситуации мы бы умножили наши входные данные, X, на некоторые известные веса, W, и добавили бы известное смещение B. Это вернет наши целевые значения Y для правильных значений W и B. угадать правильные значения W и B с первого взгляда, но есть несколько способов продвинуться вперед. Мы могли бы выполнить сеточный или случайный поиск по матрице значений, чтобы перебрать это, но это не очень сложный метод, и он не обязательно найдет минимумы для скрытого пространства, если эта область находится за пределами определенной области поиска. Итак, сначала мы создадим случайные веса и смещения, снова требующие grad для автодифференциации. Веса имеют форму 2x3, мы умножим входные данные на транспонирование этой матрицы, чтобы получить желаемую форму целевого тензора. Имея это в виду, мы можем создать нашу «модель» для этой проблемы, которая будет представлять собой матрицу X, умноженную на транспонирование весов (Wt ()) + смещение B. Также важно понимать, насколько далек каждый прогноз от основная истина, поэтому нам также необходимо определить функцию потерь для измерения степени ошибки. Мы будем использовать метрику среднеквадратичной ошибки, MSE, которая определяется ниже как нормализованный квадрат ошибки.

Итак, с определенной нашей моделью и функцией для расчета наших потерь, мы теперь можем итеративно начать поиск нашей функции потерь, минимизируя веса, чтобы решить эту регрессию. Для этого мы создадим обучающий цикл для заданного количества эпох. В каждую эпоху мы будем создавать прогнозы на основе наших входных данных, используя модель, которую мы только что определили, и случайным образом заданные веса, W, и смещение, B. Для каждого набора прогнозов мы будем вычислять потери, а затем распространять эти потери в обратном направлении с помощью вызова autograd. назад (). Затем мы обновим веса и смещения, вычтя их градиент, умноженный на небольшое значение. Мы вычитаем, потому что это приведет к понижению функции потерь по градиенту в сторону области с меньшими потерями. Затем градиент будет очищен, чтобы мы не накапливали их, и следующая эпоха начнется с этих обновленных весов и смещений, а цикл обучения продолжит этот цикл: 1) Прогнозирование, 2) расчет потерь, 3) обратное распространение, 4 ) обновление модели за заданное количество эпох.

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

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

Использование встроенных методов NN в PyTorch

Теперь мы рассмотрели строительные блоки линейной регрессии, от обычного уравнения до того, как использовать инструменты автодифференциации для оптимизации весов и смещений, и мы определили наши собственные метрики потерь и точно настроили модель LR, чтобы разумно предсказать результат для набора входы. Следующим этапом сложности является воссоздание этой модели с использованием предварительно созданного фреймворка PyTorch и построение модели как однослойной нейронной сети с линейным преобразованием. Делая это, мы также можем воспользоваться преимуществами встроенных функций потерь и оптимизаторов, которые значительно упростят наш цикл обучения. Ниже приведен код для создания точно такой же модели с использованием PyTorch nn.Module и nn.Linear для прямого прохода. Такое решение проще написать, поскольку нам не нужно определять каждую функцию самостоятельно. Он также имеет дополнительное преимущество в виде нескольких заранее написанных оптимизаторов, если мы хотим сделать что-то более сложное, чем градиентный спуск, например, использование оптимизатора Adam или различные функции потерь и даже динамическое изменение скорости обучения путем добавления планировщиков скорости обучения. Важно отметить, что теперь, когда мы создали каждый отдельный шаг с нуля, мы лучше понимаем, что делает каждая функция и почему.

Это приводит к примерно таким же потерям, весам модели и смещениям, что и наша вручную созданная модель и вариант нормального уравнения, но, как только что было сказано, дает несколько дополнительных преимуществ, которыми мы можем воспользоваться, потому что мы используем такую ​​надежную структуру. Определенно стоит отметить, что выбор PyTorch для этого проекта был также преднамеренным, поскольку возможность использования графических процессоров встроена в структуру. Вызывая доступное устройство, cuda: 0, если доступно, в противном случае ЦП, входы, цели и модель могут быть загружены в память графического процессора и аппаратно ускорены. Это становится более важным по мере того, как объем данных становится больше, и конвергенция этих моделей становится более трудоемкой задачей.

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