.. _sec_calculus: Математический анализ ===================== Долгое время вопрос о том, как вычислить площадь круга, оставался загадкой. Затем в Древней Греции математик Архимед предложил гениальную идею вписать в круг последовательность многоугольников с возрастающим числом вершин (:numref:`fig_circle_area`). Для многоугольника с :math:`n` вершинами мы получаем :math:`n` треугольников. Высота каждого треугольника стремится к радиусу :math:`r` по мере того, как мы увеличиваем число разбиений круга. В то же время его основание стремится к :math:`2 \pi r/n`, поскольку отношение длины дуги к длине хорды стремится к 1 при большом числе вершин. Таким образом, площадь многоугольника стремится к :math:`n \cdot r \cdot \frac{1}{2} (2 \pi r/n) = \pi r^2`. .. _fig_circle_area: .. figure:: ../img/polygon-circle.svg Нахождение площади круга с помощью предельного процесса. Этот предельный переход лежит в основе как *дифференциального*, так и *интегрального исчисления*. Первое из них позволяет понять, как изменить значение функции, манипулируя её аргументами. Это оказывается полезным при решении *задач оптимизации*, с которыми мы сталкиваемся в *глубоком обучении*, где мы многократно обновляем параметры, чтобы уменьшить *функцию потерь*. Оптимизация отвечает на вопрос, как наилучшим образом подогнать наши модели к обучающим данным, а исчисление является её ключевым предварительным условием. Однако не следует забывать, что наша конечная цель — хорошо работать с *ранее невиданными* данными. Эта проблема называется *обобщающей способностью* и станет одним из основных направлений рассмотрения в других главах. .. raw:: latex \diilbookstyleinputcell .. code:: python %matplotlib inline import numpy as np from matplotlib_inline import backend_inline from d2l import torch as d2l Производные и дифференцирование ------------------------------- Проще говоря, *производная* — это скорость изменения функции при изменении её аргументов. Производные показывают, насколько быстро функция потерь увеличивалась бы или уменьшалась, если бы мы *увеличили* или *уменьшили* каждый параметр на бесконечно малую величину. Формально для функций :math:`f: \mathbb{R} \rightarrow \mathbb{R}`, отображающих скаляры в скаляры, производная :math:`f` в точке :math:`x` определяется как .. math:: f'(x) = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h}. :label: eq_derivative Этот член в правой части называется *пределом* и показывает, что происходит со значением выражения, когда указанная переменная приближается к определённому значению. Этот предел указывает, к чему стремится отношение возмущения :math:`h` к изменению значения функции :math:`f(x + h) - f(x)` при стремлении его размера к нулю. Когда :math:`f'(x)` существует, :math:`f` называют *дифференцируемой* в точке :math:`x`; а когда :math:`f'(x)` существует для всех :math:`x` из некоторого множества, например, интервала :math:`[a,b]`, мы говорим, что :math:`f` дифференцируема на этом множестве. Не все функции дифференцируемы, включая многие из тех, которые мы хотим оптимизировать, такие как точность и площадь под ROC-кривой (AUC). Однако, поскольку вычисление производной функции потерь является ключевым шагом почти во всех алгоритмах обучения глубоких нейронных сетей, мы часто оптимизируем вместо неё дифференцируемую *суррогатную* функцию. Мы можем интерпретировать производную :math:`f'(x)` как *мгновенную* скорость изменения :math:`f(x)` по :math:`x`. Разберём интуитивное понимание на примере. Определим :math:`u = f(x) = 3x^2-4x`. .. raw:: latex \diilbookstyleinputcell .. code:: python def f(x): return 3 * x ** 2 - 4 * x Полагая :math:`x=1`, мы видим, что :math:`\frac{f(x+h) - f(x)}{h}` стремится к :math:`2` при стремлении :math:`h` к :math:`0`. Хотя этот эксперимент не обладает строгостью математического доказательства, мы можем быстро убедиться, что действительно :math:`f'(1) = 2`. .. raw:: latex \diilbookstyleinputcell .. code:: python for h in 10.0**np.arange(-1, -6, -1): print(f'h={h:.5f}, numerical limit={(f(1+h)-f(1))/h:.5f}') Существует несколько эквивалентных нотаций для обозначения производных. Учитывая :math:`y = f(x)`, следующие выражения эквивалентны: .. math:: f'(x) = y' = \frac{dy}{dx} = \frac{df}{dx} = \frac{d}{dx} f(x) = Df(x) = D_x f(x), где символы :math:`\frac{d}{dx}` и :math:`D` — это *операторы дифференцирования*. Ниже приведены производные некоторых распространённых функций: .. math:: \begin{aligned} \frac{d}{dx} C & = 0 && \textrm{for any constant $C$} \\ \frac{d}{dx} x^n & = n x^{n-1} && \textrm{for } n \neq 0 \\ \frac{d}{dx} e^x & = e^x \\ \frac{d}{dx} \ln x & = x^{-1}. \end{aligned} Функции, составленные из дифференцируемых функций, часто сами являются дифференцируемыми. Следующие правила оказываются полезными при работе с композициями любых дифференцируемых функций :math:`f` и :math:`g`, а также констант :math:`C`. .. math:: \begin{aligned} \frac{d}{dx} [C f(x)] & = C \frac{d}{dx} f(x) && \textrm{Constant multiple rule} \\ \frac{d}{dx} [f(x) + g(x)] & = \frac{d}{dx} f(x) + \frac{d}{dx} g(x) && \textrm{Sum rule} \\ \frac{d}{dx} [f(x) g(x)] & = f(x) \frac{d}{dx} g(x) + g(x) \frac{d}{dx} f(x) && \textrm{Product rule} \\ \frac{d}{dx} \frac{f(x)}{g(x)} & = \frac{g(x) \frac{d}{dx} f(x) - f(x) \frac{d}{dx} g(x)}{g^2(x)} && \textrm{Quotient rule} \end{aligned} Используя это, мы можем применить правила для нахождения производной :math:`3 x^2 - 4x` через .. math:: \frac{d}{dx} [3 x^2 - 4x] = 3 \frac{d}{dx} x^2 - 4 \frac{d}{dx} x = 6x - 4. Подстановка :math:`x = 1` показывает, что производная действительно равна :math:`2` в этой точке. Обратите внимание, что производные показывают *наклон* функции в конкретной точке. Утилиты визуализации -------------------- Мы можем визуализировать градиенты функций с помощью библиотеки ``matplotlib`` . Для этого нам нужно определить несколько функций. Как следует из названия, ``use_svg_display`` указывает ``matplotlib`` выводить графики в формате SVG для получения более чётких изображений. Комментарий ``#@save`` — это специальный модификатор, который позволяет сохранить любую функцию, класс или другой блок кода в пакете ``d2l``, чтобы вызывать их позже без повторения кода, например, через ``d2l.use_svg_display()``. .. raw:: latex \diilbookstyleinputcell .. code:: python def use_svg_display(): #@save """Use the svg format to display a plot in Jupyter.""" backend_inline.set_matplotlib_formats('svg') Кроме того, мы можем задавать размеры рисунков с помощью ``set_figsize``. Поскольку оператор импорта ``from matplotlib import pyplot as plt`` был отмечен через ``#@save`` в пакете ``d2l``, мы можем вызывать ``d2l.plt``. .. raw:: latex \diilbookstyleinputcell .. code:: python def set_figsize(figsize=(3.5, 2.5)): #@save """Set the figure size for matplotlib.""" use_svg_display() d2l.plt.rcParams['figure.figsize'] = figsize Функция ``set_axes`` позволяет связывать оси с различными свойствами, включая метки, диапазоны и масштабы. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend): """Set the axes for matplotlib.""" axes.set_xlabel(xlabel), axes.set_ylabel(ylabel) axes.set_xscale(xscale), axes.set_yscale(yscale) axes.set_xlim(xlim), axes.set_ylim(ylim) if legend: axes.legend(legend) axes.grid() С помощью этих трёх функций мы можем определить функцию ``plot`` для наложения нескольких кривых. Значительная часть кода здесь предназначена лишь для проверки соответствия размеров и форм входных данных. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def plot(X, Y=None, xlabel=None, ylabel=None, legend=[], xlim=None, ylim=None, xscale='linear', yscale='linear', fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None): """Plot data points.""" def has_one_axis(X): # True if X (tensor or list) has 1 axis return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list) and not hasattr(X[0], "__len__")) if has_one_axis(X): X = [X] if Y is None: X, Y = [[]] * len(X), X elif has_one_axis(Y): Y = [Y] if len(X) != len(Y): X = X * len(Y) set_figsize(figsize) if axes is None: axes = d2l.plt.gca() axes.cla() for x, y, fmt in zip(X, Y, fmts): axes.plot(x,y,fmt) if len(x) else axes.plot(y,fmt) set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend) Теперь мы можем построить график функции :math:`u = f(x)` и её касательной :math:`y = 2x - 3` в точке :math:`x=1`, где коэффициент :math:`2` — это угловой коэффициент касательной. .. raw:: latex \diilbookstyleinputcell .. code:: python x = np.arange(0, 3, 0.1) plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)']) .. _subsec_calculus-grad: Частные производные и градиенты ------------------------------- До сих пор мы рассматривали дифференцирование функций одной переменной. В глубоком обучении нам также приходится работать с функциями *многих* переменных. Мы кратко введём понятия производной, применимые к таким *многомерным* функциям. Пусть :math:`y = f(x_1, x_2, \ldots, x_n)` — функция от :math:`n` переменных. *Частная производная* :math:`y` по параметру :math:`x_i`, являющемуся её :math:`i^\textrm{th}` параметром, равна .. math:: \frac{\partial y}{\partial x_i} = \lim_{h \rightarrow 0} \frac{f(x_1, \ldots, x_{i-1}, x_i+h, x_{i+1}, \ldots, x_n) - f(x_1, \ldots, x_i, \ldots, x_n)}{h}. Чтобы вычислить :math:`\frac{\partial y}{\partial x_i}`, мы можем рассматривать :math:`x_1, \ldots, x_{i-1}, x_{i+1}, \ldots, x_n` как константы и найти производную :math:`y` по :math:`x_i`. Следующие обозначения для частных производных являются общепринятыми и означают одно и то же: .. math:: \frac{\partial y}{\partial x_i} = \frac{\partial f}{\partial x_i} = \partial_{x_i} f = \partial_i f = f_{x_i} = f_i = D_i f = D_{x_i} f. Мы можем объединить частные производные многомерной функции по всем её переменным, чтобы получить вектор, называемый *градиентом* функции. Предположим, что входом функции :math:`f: \mathbb{R}^n \rightarrow \mathbb{R}` является :math:`n`-мерный вектор :math:`\mathbf{x} = [x_1, x_2, \ldots, x_n]^\top`, а выходом — скаляр. Градиент функции :math:`f` по аргументу :math:`\mathbf{x}` — это вектор из :math:`n` частных производных: .. math:: \nabla_{\mathbf{x}} f(\mathbf{x}) = \left[\partial_{x_1} f(\mathbf{x}), \partial_{x_2} f(\mathbf{x}), \ldots \partial_{x_n} f(\mathbf{x})\right]^\top. Когда это не вызывает неоднозначности, :math:`\nabla_{\mathbf{x}} f(\mathbf{x})` обычно заменяют на :math:`\nabla f(\mathbf{x})`. Следующие правила полезны при дифференцировании функций многих переменных: - Для всех :math:`\mathbf{A} \in \mathbb{R}^{m \times n}` выполняются :math:`\nabla_{\mathbf{x}} \mathbf{A} \mathbf{x} = \mathbf{A}^\top` и :math:`\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} = \mathbf{A}`. - Для квадратных матриц :math:`\mathbf{A} \in \mathbb{R}^{n \times n}` выполняется, что :math:`\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} \mathbf{x} = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}`, и в частности :math:`\nabla_{\mathbf{x}} \|\mathbf{x} \|^2 = \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{x} = 2\mathbf{x}`. Аналогично, для любой матрицы :math:`\mathbf{X}` выполняется равенство :math:`\nabla_{\mathbf{X}} \|\mathbf{X} \|_\textrm{F}^2 = 2\mathbf{X}`. Цепное правило -------------- В глубоком обучении вычисление градиентов часто затруднено, поскольку мы имеем дело с глубоко вложенными функциями (функциями от функций от функций…). К счастью, это решает *цепное правило*. Вернёмся к функциям одной переменной: предположим, что :math:`y = f(g(x))`, а базовые функции :math:`y=f(u)` и :math:`u=g(x)` дифференцируемы. Согласно цепному правилу, .. math:: \frac{dy}{dx} = \frac{dy}{du} \frac{du}{dx}. Возвращаясь к функциям многих переменных, предположим, что :math:`y = f(\mathbf{u})` зависит от переменных :math:`u_1, u_2, \ldots, u_m`, где каждая :math:`u_i = g_i(\mathbf{x})` зависит от переменных :math:`x_1, x_2, \ldots, x_n`, т. е. :math:`\mathbf{u} = g(\mathbf{x})`. Тогда цепное правило гласит, что .. math:: \frac{\partial y}{\partial x_{i}} = \frac{\partial y}{\partial u_{1}} \frac{\partial u_{1}}{\partial x_{i}} + \frac{\partial y}{\partial u_{2}} \frac{\partial u_{2}}{\partial x_{i}} + \ldots + \frac{\partial y}{\partial u_{m}} \frac{\partial u_{m}}{\partial x_{i}} \ \textrm{ and so } \ \nabla_{\mathbf{x}} y = \mathbf{A} \nabla_{\mathbf{u}} y, где :math:`\mathbf{A} \in \mathbb{R}^{n \times m}` — это *матрица*, содержащая производную вектора :math:`\mathbf{u}` по вектору :math:`\mathbf{x}`. Таким образом, вычисление градиента требует вычисления произведения вектора на матрицу. Это одна из ключевых причин, по которым линейная алгебра является столь неотъемлемым компонентом при создании систем глубокого обучения. Обсуждение ---------- Хотя мы лишь поверхностно коснулись этой глубокой темы, ряд концепций уже становится ясным: во-первых, правила дифференцирования сложных функций можно применять регулярно, что позволяет вычислять градиенты *автоматически*. Эта задача не требует креативности, поэтому мы можем направить наши когнитивные ресурсы на другие цели. Во-вторых, вычисление производных вектор-функций требует умножения матриц при обходе графа зависимостей переменных от выхода ко входу. В частности, этот граф обходится в *прямом* направлении при вычислении функции и в *обратном* направлении при вычислении градиентов. В последующих главах мы формально введем обратное распространение ошибки — вычислительную процедуру для применения цепного правила. С точки зрения оптимизации, градиенты позволяют определить, как изменить параметры модели, чтобы уменьшить функцию потерь, и каждый шаг алгоритмов оптимизации, рассматриваемых в этой книге, потребует вычисления градиента. Упражнения ---------- 1. До сих пор мы принимали правила дифференцирования как само собой разумеющиеся. Используя определение производной и пределы, докажите свойства для (i) :math:`f(x) = c`, (ii) :math:`f(x) = x^n`, (iii) :math:`f(x) = e^x` и (iv) :math:`f(x) = \log x`. 2. Аналогичным образом докажите правила дифференцирования произведения, суммы и частного, исходя из первых принципов. 3. Докажите, что правило умножения на константу является частным случаем правила произведения. 4. Вычислите производную :math:`f(x) = x^x`. 5. Что означает, что :math:`f'(x) = 0` для некоторого :math:`x`? Приведите пример функции :math:`f` и точки :math:`x`, для которой это может выполняться. 6. Постройте график функции :math:`y = f(x) = x^3 - \frac{1}{x}` и проведите касательную к ней в точке :math:`x = 1`. 7. Найдите градиент функции :math:`f(\mathbf{x}) = 3x_1^2 + 5e^{x_2}`. 8. Каков градиент функции :math:`f(\mathbf{x}) = \|\mathbf{x}\|_2`? Что происходит для :math:`\mathbf{x} = \mathbf{0}`? 9. Можете ли вы записать цепное правило для случая, когда :math:`u = f(x, y, z)` и :math:`x = x(a, b)`, :math:`y = y(a, b)` и :math:`z = z(a, b)`? 10. Дана обратимая функция :math:`f(x)`. Вычислите производную её обратной функции :math:`f^{-1}(x)`. Здесь выполняется соотношение :math:`f^{-1}(f(x)) = x`, а обратно :math:`f(f^{-1}(y)) = y`. Подсказка: используйте эти свойства в выводе. `Обсуждения `__