Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.1 Линейная регрессия

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

В большинстве задач машинного обучения речь идёт о предсказании (prediction). Если нужно предсказать числовое значение, мы имеем дело с задачей регрессии. Типичные примеры: предсказание цены (недвижимости, акций и т. п.), предсказание времени пребывания пациента в стационаре, предсказание спроса (например, объёма продаж в розничной торговле). Но не всякое предсказание — задача регрессии. В следующих разделах мы рассмотрим задачи классификации, в которых цель состоит в том, чтобы определить, к какой из категорий относятся данные.

3.1.1 Основные элементы линейной регрессии

Линейная регрессия (linear regression) появилась в начале XIX века. Это самый простой и самый распространённый из стандартных инструментов регрессии. Она опирается на несколько базовых предположений. Во-первых, связь между признаками и целевой величиной линейна: можно представить как взвешенную сумму элементов , и при этом допускается небольшой шум в наблюдениях. Во-вторых, шум считается «обычным», например распределённым по нормальному закону.

Чтобы объяснить линейную регрессию, рассмотрим практический пример. Допустим, мы хотим оценивать стоимость дома (в долларах) по его площади (в квадратных футах) и возрасту (в годах). Для построения модели, которая предсказывает цену, нужен реальный набор данных. Он включает цену продажи, площадь и возраст каждого дома. В терминах машинного обучения такой набор называется обучающим набором данных (training data set) или обучающим набором (training set). Каждая строка данных (например, сведения об одной сделке с домом) называется примером (sample), или точкой данных (data point), или экземпляром данных (data instance). Величину, которую мы хотим предсказать (в нашем примере — цену), называют меткой (label) или целевой величиной (target). Независимые переменные, на основе которых делается предсказание (площадь и возраст), называются признаками (feature) или ковариатами (covariate).

Количество примеров в наборе данных обозначим через . Вход примера с индексом запишем как , а соответствующую метку — как .

3.1.1.1 Линейная модель

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

В формуле (3.1.1) и называются весами (weight); они определяют, насколько каждый признак влияет на предсказание. Величина называется смещением (bias), сдвигом (offset) или свободным членом (intercept). Смещение задаёт предсказание в случае, когда все признаки равны нулю. В реальности у дома не бывает нулевой площади, и возраст ровно в ноль лет тоже встречается редко, но смещение всё равно необходимо: без него выразительность модели была бы ограничена. Строго говоря, формула (3.1.1) задаёт аффинное преобразование (affine transformation) входных признаков. Оно состоит из линейного преобразования, которое даёт взвешенная сумма признаков, и сдвига (translation) на величину смещения.

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

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

Соберём все признаки в вектор , а все веса — в вектор . Тогда модель записывается компактно через скалярное произведение:

В формуле (3.1.2) вектор соответствует признакам одного примера. Для всего набора из примеров удобно использовать матрицу : каждая её строка — это пример, а каждый столбец — отдельный признак.

Для всей матрицы признаков вектор предсказаний вычисляется как произведение матрицы на вектор:

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

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

Прежде чем искать наилучшие параметры модели (model parameters) и , нам нужны две вещи: (1) способ измерить качество модели; (2) способ обновлять модель так, чтобы её предсказания становились лучше.

3.1.1.2 Функция потерь

Прежде чем подгонять модель под данные, нужно определить меру качества подгонки. Функция потерь (loss function) измеряет расхождение между истинным значением целевой величины и предсказанным. Обычно потери выбирают неотрицательными: чем меньше потери, тем лучше, а при идеальном предсказании потери равны нулю. В задачах регрессии чаще всего используют квадратичную функцию потерь. Если для примера предсказание равно , а истинная метка — , квадратичная ошибка задаётся формулой:

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

Рис. 3.1.1. Подгонка данных с помощью линейной модели

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

При обучении мы ищем параметры (), которые минимизируют суммарные потери по всем обучающим примерам:

3.1.1.3 Аналитическое решение

Линейная регрессия — очень простая задача оптимизации. В отличие от большинства моделей, которые будут рассмотрены в книге, её решение можно записать одной формулой. Такое решение называют аналитическим (analytical solution). Сначала включим смещение в вектор параметров , добавив к матрице признаков столбец из единиц. Тогда задача предсказания сводится к минимизации . У этой функции есть единственная критическая точка, которая соответствует минимуму потерь на всей области определения. Приравняв производную потерь по нулю, получаем аналитическое решение:

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

3.1.1.4 Стохастический градиентный спуск по минибатчам

Даже когда аналитического решения нет, модель можно эффективно обучать. В книге мы будем использовать градиентный спуск (gradient descent) — метод, который подходит почти для любой модели глубокого обучения. Он постепенно уменьшает ошибку, сдвигая параметры в направлении убывания функции потерь.

Самый простой вариант градиентного спуска: вычислить производную функции потерь (средних потерь по всему набору данных) по параметрам модели, то есть градиент. Но на практике это может быть очень медленно, потому что перед каждым обновлением параметров пришлось бы проходить весь набор данных. Поэтому обычно на каждом шаге случайно выбирают небольшой минибатч примеров. Такой вариант называется стохастическим градиентным спуском по минибатчам (minibatch stochastic gradient descent).

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

Это обновление записывается формулой ( обозначает частную производную):

Итак, алгоритм состоит из следующих шагов: (1) инициализировать параметры модели, например случайными значениями; (2) многократно выбирать случайный минибатч примеров и сдвигать параметры в направлении антиградиента. Для квадратичных потерь и аффинного преобразования обновления можно записать явно:

В формуле (3.1.4) и — векторы. Такая векторная запись понятнее, чем поэлементная запись через . — количество примеров в минибатче, то есть размер батча (batch size). — скорость обучения (learning rate). Размер батча и скорость обучения обычно задаются заранее вручную, а не выучиваются моделью. Такие величины, которые можно настраивать, но которые не обновляются в процессе обучения, называются гиперпараметрами (hyperparameter). Подбор гиперпараметров (hyperparameter tuning) — это процесс их выбора. Гиперпараметры обычно подбирают по результатам обучения, оценивая качество модели на отдельном проверочном наборе данных (validation dataset).

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

Линейная регрессия — задача, у которой на всей области есть только один минимум. Однако для сложных моделей, таких как глубокие нейронные сети, поверхность функции потерь обычно содержит много минимумов. Практики глубокого обучения редко тратят много усилий на то, чтобы найти набор параметров с наименьшими потерями на обучающем наборе. Гораздо сложнее найти параметры, которые дают небольшие потери на данных, которых модель никогда не видела. Эта задача называется обобщением (generalization).

3.1.1.5 Предсказания с помощью обученной модели

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

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

3.1.2 Векторизация для ускорения вычислений

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

Чтобы показать, насколько это важно, сравним два способа сложения векторов. Создадим два вектора из единиц длины 10000. В первом способе будем обходить векторы циклом for на Python; во втором — воспользуемся перегруженным оператором +.

MXNet:

%matplotlib inline
from d2l import mxnet as d2l
import math
from mxnet import np
import time

PyTorch:

%matplotlib inline
from d2l import torch as d2l
import math
import torch
import numpy as np
import time

TensorFlow:

%matplotlib inline
from d2l import tensorflow as d2l
import math
import tensorflow as tf
import numpy as np
import time

PaddlePaddle:

%matplotlib inline
from d2l import paddle as d2l
import warnings
warnings.filterwarnings("ignore")
import math
import numpy as np
import time
import paddle
n = 10000
a = d2l.ones([n])
b = d2l.ones([n])

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

class Timer:  #@save
    """Запись времени выполнения нескольких запусков"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """Запуск таймера"""
        self.tik = time.time()

    def stop(self):
        """Остановка таймера и запись времени в список"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """Возврат среднего времени"""
        return sum(self.times) / len(self.times)

    def sum(self):
        """Возврат суммарного времени"""
        return sum(self.times)

    def cumsum(self):
        """Возврат накопленного времени"""
        return np.array(self.times).cumsum().tolist()

Теперь можно измерить время работы.

Сначала используем цикл for, складывая элементы по одному.

MXNet, PyTorch:

c = d2l.zeros(n)
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
f'{timer.stop():.5f} сек'

TensorFlow:

c = tf.Variable(d2l.zeros(n))
timer = Timer()
for i in range(n):
    c[i].assign(a[i] + b[i])
f'{timer.stop():.5f} сек'

PaddlePaddle:

c = d2l.zeros([n])
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
f'{timer.stop():.5f} сек'

Теперь используем перегруженный оператор + для поэлементного сложения.

timer.start()
d = a + b
f'{timer.stop():.5f} сек'

Результат очевиден: второй способ значительно быстрее первого. Векторизованный код обычно даёт ускорение на порядки. Кроме того, мы переносим больше математических операций в библиотеку и меньше пишем вычислений вручную, что снижает вероятность ошибок.

3.1.3 Нормальное распределение и квадратичные потери

Далее объясним функцию потерь, исходя из предположения о распределении шума.

Нормальное распределение тесно связано с линейной регрессией. Нормальное распределение (normal distribution), также называемое гауссовым (Gaussian distribution), впервые применил в астрономии немецкий математик Гаусс. Если случайная величина имеет среднее значение и дисперсию (стандартное отклонение ), то её плотность вероятности задаётся формулой:

Теперь определим функцию Python для нормального распределения.

def normal(x, mu, sigma):
    p = 1 / math.sqrt(2 * math.pi * sigma**2)
    return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)

Теперь построим график нормального распределения.

MXNet:

# Снова используем numpy для построения графика
x = np.arange(-7, 7, 0.01)
# Пары среднего значения и стандартного отклонения
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x.asnumpy(), [normal(x, mu, sigma).asnumpy() for mu, sigma in params], xlabel='x',
         ylabel='p(x)', figsize=(4.5, 2.5),
         legend=[f'среднее {mu}, ст. откл. {sigma}' for mu, sigma in params])

PyTorch, TensorFlow, PaddlePaddle:

# Снова используем numpy для построения графика
x = np.arange(-7, 7, 0.01)

# Пары среднего значения и стандартного отклонения
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x, [normal(x, mu, sigma) for mu, sigma in params], xlabel='x',
         ylabel='p(x)', figsize=(4.5, 2.5),
         legend=[f'среднее {mu}, ст. откл. {sigma}' for mu, sigma in params])

Как видно, изменение среднего значения сдвигает распределение вдоль оси , а увеличение дисперсии делает распределение более пологим и снижает его пик.

Квадратичные потери (среднеквадратичная ошибка) можно использовать в линейной регрессии потому, что мы считаем шум в наблюдениях нормально распределённым. Наблюдения описываются моделью с шумом:

где .

Теперь можно записать правдоподобие (likelihood) того, что при данном наблюдается конкретное значение :

По принципу максимального правдоподобия оптимальные значения и — это те, при которых правдоподобие всего набора данных максимально:

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

Если считать фиксированной константой, первое слагаемое можно отбросить, потому что оно не зависит от и . Второе слагаемое с точностью до константного множителя совпадает с уже введённой среднеквадратичной ошибкой. К тому же решение задачи не зависит от . Итак, при гауссовом шуме минимизация среднеквадратичной ошибки эквивалентна оценке линейной модели по принципу максимального правдоподобия.

3.1.4 От линейной регрессии к глубоким сетям

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

3.1.4.1 Диаграмма нейронной сети

Специалисты по глубокому обучению любят рисовать схемы, чтобы наглядно показывать происходящее в модели. На рис. 3.1.2 линейная регрессия изображена в виде нейронной сети. Схема показывает только структуру связей, то есть какой вход соединён с каким выходом, а значения весов и смещения на ней не отражены.

Рис. 3.1.2. Линейная регрессия — однослойная нейронная сеть

В сети с рис. 3.1.2 входами служат , поэтому число входов (или размерность признаков, feature dimensionality) входного слоя равно . Выход сети — , значит, число выходов выходного слоя равно 1. Входные значения заданы, а вычислительный нейрон здесь один. Поскольку вычисления происходят в нейронах, при подсчёте числа слоёв входной слой обычно не учитывают. Поэтому у сети с рис. 3.1.2 один слой. Линейную регрессию можно рассматривать как нейронную сеть, состоящую из одного искусственного нейрона, или как однослойную нейронную сеть.

В линейной регрессии каждый вход соединён с каждым выходом (в данном случае выход один). Такое преобразование (выходной слой на рис. 3.1.2) называют полносвязным слоем (fully-connected layer), или плотным слоем (dense layer). Сети, построенные из таких слоёв, подробно рассматриваются в следующей главе.

3.1.4.2 Биология

Линейная регрессия была придумана в 1795 году, задолго до вычислительной нейробиологии, поэтому описывать её как нейронную сеть может показаться неуместным. Однако почему специалисты по кибернетике и нейробиологии Уоррен Маккаллок и Уолтер Питтс, начав разрабатывать модель искусственного нейрона, взяли в качестве отправной точки именно линейную модель? Посмотрим на рис. 3.1.3: это изображение биологического нейрона. На нём видны дендриты (входные окончания), ядро (аналог центрального процессора), аксон (выходное волокно) и концевые разветвления аксона (выходные окончания). Аксон соединяется с другими нейронами через синапсы.

Рис. 3.1.3. Настоящий нейрон

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

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

Сегодняшние исследования в глубоком обучении почти не черпают вдохновение напрямую из нейронауки. Стюарт Рассел и Питер Норвиг в своём классическом учебнике по искусственному интеллекту Artificial Intelligence: A Modern Approach (Russell and Norvig, 2016) сравнивают это с авиацией: хотя самолёты, возможно, и вдохновлены птицами, на протяжении веков орнитология не была главным двигателем авиационных инноваций. Точно так же сегодня источником вдохновения для глубокого обучения служат математика, статистика и информатика — не меньше, а то и больше, чем биология.

Резюме

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

Упражнения

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

MXNet: Обсуждение

PyTorch: Обсуждение

TensorFlow: Обсуждение

PaddlePaddle: Обсуждение