.. _sec_linear_concise: Краткая реализация линейной регрессии ===================================== За последнее десятилетие глубокое обучение пережило своего рода кембрийский взрыв. Число методов, приложений и алгоритмов далеко превосходит достижения предыдущих десятилетий. Это стало возможным благодаря удачному сочетанию нескольких факторов, одним из которых являются мощные бесплатные инструменты, предлагаемые рядом фреймворков глубокого обучения с открытым исходным кодом. Theano :cite:`Bergstra.Breuleux.Bastien.ea.2010`, DistBelief :cite:`Dean.Corrado.Monga.ea.2012` и Caffe :cite:`Jia.Shelhamer.Donahue.ea.2014`, пожалуй, представляют первое поколение таких фреймворков, получивших широкое распространение. В отличие от более ранних (пионерских) работ, таких как SN2 (Simulateur Neuristique) :cite:`Bottou.Le-Cun.1988`, которые обеспечивали опыт программирования, похожий на Lisp, современные фреймворки предлагают автоматическое дифференцирование и удобство использования Python. Эти фреймворки позволяют автоматизировать и модуляризировать рутинную работу по реализации алгоритмов обучения на основе градиента. В :numref:`sec_linear_scratch` мы опирались лишь на (i) тензоры для хранения данных и операций линейной алгебры; и (ii) автоматическое дифференцирование для вычисления градиентов. На практике, поскольку итераторы данных, функции потерь, оптимизаторы и слои нейронных сетей являются общими компонентами, современные библиотеки реализуют их за нас. В этом разделе мы покажем, как компактно реализовать модель линейной регрессии из :numref:`sec_linear_scratch` с использованием высокоуровневых API фреймворков глубокого обучения. .. raw:: latex \diilbookstyleinputcell .. code:: python import numpy as np import torch from torch import nn from d2l import torch as d2l Определение модели ------------------ Когда мы реализовывали линейную регрессию с нуля в :numref:`sec_linear_scratch`, мы явно определяли параметры модели и вручную прописывали вычисления для получения выходных данных с помощью базовых операций линейной алгебры. Вы *должны* уметь это делать. Но когда модели станут сложнее, а вам придется заниматься этим практически каждый день, вы будете рады такой помощи. Ситуация аналогична написанию собственного блога с нуля. Сделать это один или два раза — полезно и поучительно, но вы были бы плохим веб-разработчиком, если бы потратили месяц на изобретение велосипеда. Для стандартных операций мы можем **использовать предопределённые слои фреймворка**, что позволяет сосредоточиться на слоях, используемых для построения модели, а не на деталях их реализации. Вспомним архитектуру однослойной сети, описанную в :numref:`fig_single_neuron`. Этот слой называется *полносвязным*, поскольку каждый его вход соединён с каждым выходом посредством умножения матрицы на вектор. В PyTorch полносвязный слой определен в классах ``Linear`` и ``LazyLinear`` (доступны начиная с версии 1.8.0). Последний позволяет пользователям указывать *только* выходную размерность, тогда как первый дополнительно требует указания количества входов. Указание формы входа неудобно и может потребовать нетривиальных вычислений (например, в свёрточных слоях). Поэтому для простоты мы будем использовать такие «ленивые» слои, whenever это возможно. .. raw:: latex \diilbookstyleinputcell .. code:: python class LinearRegression(d2l.Module): #@save """The linear regression model implemented with high-level APIs.""" def __init__(self, lr): super().__init__() self.save_hyperparameters() self.net = nn.LazyLinear(1) self.net.weight.data.normal_(0, 0.01) self.net.bias.data.fill_(0) В методе ``forward`` мы просто вызываем встроенный метод ``__call__`` предопределённых слоёв для вычисления результатов. .. raw:: latex \diilbookstyleinputcell .. code:: python @d2l.add_to_class(LinearRegression) #@save def forward(self, X): return self.net(X) Определение функции потерь -------------------------- Класс ``MSELoss`` вычисляет среднеквадратичную ошибку (без множителя :math:`1/2` в :eq:`eq_mse`). По умолчанию ``MSELoss`` возвращает среднее значение функции потерь по примерам. Это быстрее (и проще в использовании), чем реализовывать собственную функцию. .. raw:: latex \diilbookstyleinputcell .. code:: python @d2l.add_to_class(LinearRegression) #@save def loss(self, y_hat, y): fn = nn.MSELoss() return fn(y_hat, y) Определение алгоритма оптимизации --------------------------------- Мини-батч SGD — стандартный инструмент оптимизации нейронных сетей, поэтому PyTorch поддерживает его наряду с рядом вариаций этого алгоритма в модуле ``optim``. При создании **объекта ``SGD``** мы указываем параметры, подлежащие оптимизации, которые можно получить из модели с помощью ``self.parameters()``, а также скорость обучения (``self.lr``), требуемую алгоритмом оптимизации. .. raw:: latex \diilbookstyleinputcell .. code:: python @d2l.add_to_class(LinearRegression) #@save def configure_optimizers(self): return torch.optim.SGD(self.parameters(), self.lr) Обучение -------- Вы могли заметить, что реализация модели с помощью высокоуровневых API фреймворка глубокого обучения требует меньше строк кода. Нам не приходилось выделять параметры по отдельности, определять функцию потерь или реализовывать мини-батч SGD. Как только мы начнем работать с гораздо более сложными моделями, преимущества высокоуровневого API возрастут значительно. Теперь, когда все основные компоненты готовы, **сам цикл обучения такой же, как тот, что мы реализовали с нуля.** Поэтому мы просто вызываем метод ``fit`` (описанный в :numref:`oo-design-training`), который опирается на реализацию метода ``fit_epoch`` из :numref:`sec_linear_scratch`, для обучения нашей модели. .. raw:: latex \diilbookstyleinputcell .. code:: python model = LinearRegression(lr=0.03) data = d2l.SyntheticRegressionData(w=torch.tensor([2, -3.4]), b=4.2) trainer = d2l.Trainer(max_epochs=3) trainer.fit(model, data) Ниже мы сравниваем параметры модели, полученные при обучении на конечном наборе данных, с истинными параметрами, которые сгенерировали наш набор данных. Чтобы получить доступ к параметрам, мы обращаемся к весам и смещению соответствующего слоя. Как и в нашей реализации с нуля, обратите внимание, что наши оценённые параметры близки к их истинным значениям. .. raw:: latex \diilbookstyleinputcell .. code:: python @d2l.add_to_class(LinearRegression) #@save def get_w_b(self): return (self.net.weight.data, self.net.bias.data) w, b = model.get_w_b() .. raw:: latex \diilbookstyleinputcell .. code:: python print(f'error in estimating w: {data.w - w.reshape(data.w.shape)}') print(f'error in estimating b: {data.b - b}') Итог ---- В этом разделе представлена первая реализация глубокой сети (в данной книге), позволяющая воспользоваться удобством современных фреймворков для глубокого обучения, таких как MXNet :cite:`Chen.Li.Li.ea.2015`, JAX :cite:`Frostig.Johnson.Leary.2018`, PyTorch :cite:`Paszke.Gross.Massa.ea.2019` и Tensorflow :cite:`Abadi.Barham.Chen.ea.2016`. Мы использовали настройки по умолчанию фреймворка для загрузки данных, определения слоя, функции потерь, оптимизатора и цикла обучения. Если фреймворк предоставляет все необходимые функции, как правило, целесообразно использовать их, поскольку реализации этих компонентов в библиотеках обычно сильно оптимизированы для производительности и тщательно протестированы на надёжность. В то же время не забывайте, что эти модули *могут* быть реализованы непосредственно. Это особенно важно для начинающих исследователей, которые хотят находиться на переднем крае разработки моделей, где вам придется создавать новые компоненты, которых ещё нет ни в одной из существующих библиотек. В PyTorch модуль ``data`` предоставляет инструменты для обработки данных, а модуль ``nn`` определяет большое количество слоёв нейронных сетей и распространённых функций потерь. Параметры можно инициализировать, заменяя их значения методами, названия которых заканчиваются на ``_``. Обратите внимание, что необходимо указывать входные размеры сети. Хотя на данный момент это кажется тривиальной задачей, это может иметь существенные последствия при проектировании сложных сетей с множеством слоёв. Чтобы обеспечить переносимость, требуется тщательно продумать, как параметризовать такие сети. Упражнения ---------- 1. Как нужно изменить скорость обучения, если заменить сумму потерь по мини-батчу на среднее значение потерь по мини-батчу? 2. Изучите документацию фреймворка, чтобы узнать, какие функции потерь в нём предусмотрены. В частности, замените квадратичную функцию потерь на робастную функцию потерь Хубера. То есть используйте функцию потерь .. math:: l(y,y') = \begin{cases}|y-y'| -\frac{\sigma}{2} & \textrm{ if } |y-y'| > \sigma \\ \frac{1}{2 \sigma} (y-y')^2 & \textrm{ otherwise}\end{cases} 3. Как получить доступ к градиенту весов модели? 4. Как изменение скорости обучения и количества эпох влияет на решение? Продолжает ли оно улучшаться? 5. Как меняется решение при изменении количества сгенерированных данных? 1. Постройте график ошибки оценки для :math:`\hat{\mathbf{w}} - \mathbf{w}` и :math:`\hat{b} - b` в зависимости от объёма данных. Подсказка: увеличивайте объём данных логарифмически, а не линейно, то есть 5, 10, 20, 50,…, 10 000, а не 1000, 2000,…, 10 000. 2. Почему предложенная в подсказке рекомендация является уместной? `Обсуждения `__