Статья Автор: Деникина Н.В., Деникин А.В.

Все веса сразу

Производная по каждому весу берётся по той же формуле, меняется только множитель — тот признак, при котором стоит вес. У свободного члена множителя нет: он прибавляется одинаково ко всем заказам, то есть его признак равен единице.

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

Полная база заказов

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

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

Таблица 5. Полная база: восемь заказов.

заказ км светофоров курьеров ехал, мин
1 4 2 6 20
2 2 5 4 19.5
3 6 1 2 26.5
4 1.5 3 8 13
5 5 0 5 20.5
6 3 4 1 22.5
7 7.5 2 3 32
8 2.5 6 7 21

Первые четыре строки — не те же самые, что раньше: это другая, более разнообразная выборка. Времена в ней посчитаны по тому же правилу, так что истинные веса всё те же 3, 1.5, −0.5 и 8.

Матричная запись градиента

Производные по трём весам удобно собрать одним матричным умножением, а производную по свободному члену - отдельной суммой:

e = X · w + b − y

градиент по трём весам:
∇w L = 2 · Xᵀ · e / n

производная по свободному члену:
∂L/∂b = 2 · сумма(e) / n


Первое выражение даёт три числа, второе — ещё одно. Вместе они образуют градиент по четырём параметрам.

Разберём, что здесь что.

  • X — матрица признаков: строка на каждый заказ, столбец на каждый признак. У нас 8 заказов и 3 признака, значит у X 8 строк и 3 столбца.
  • y — вектор фактических времён, по одному числу на заказ; длина 8.
  • w — вектор весов, длина 3. b — свободный член, одно число. n — число заказов, 8.
  • X · w + b — вектор прогнозов, длина 8. Вычитая y, получаем вектор промахов, тоже длины 8.
  • Xᵀ — та же матрица признаков, повёрнутая: строки стали столбцами, у неё 3 строки и 8 столбцов. Умножение Xᵀ на вектор промахов складывает произведения «промах × признак» по всем заказам. После умножения на 2/n получаем три частные производные.

Поворот матрицы нужен ровно для того, чтобы свернуть восемь заказов и оставить три признака. В нашей матрице X три столбца, поэтому её нельзя умножить на вектор из восьми промахов. После транспонирования у Xᵀ восемь столбцов — размеры согласованы.
 

Как считается спуск по четырём параметрам

Порядок тот же, что и для одного веса, только теперь на каждом шаге обновляются все четыре числа:

  1. начинаем с нулей: все четыре параметра равны 0;
  2. считаем прогнозы по всем восьми заказам и промахи;
  3. для каждого веса берём среднее от 2 · промах · свой признак, для свободного члена — среднее от 2 · промах; это четыре частные производные;
  4. вычитаем из каждого параметра lr × его производную;
  5. возвращаемся к пункту 2.

Посмотрим первый шаг. В начале все параметры нулевые, значит прогноз на каждом заказе тоже ноль, а промах равен минус факту. Четыре производные выходят такими: −190 по километрам, −120.75 по светофорам, −180.75 по курьерам, −43.75 по сборке.

Все четыре отрицательны — неудивительно, ведь нулевая модель всё недооценивает. Умножаем на lr = 0.02 и вычитаем:

вес при километрах:  0 − 0.02 · (−190) = 3.8
вес при светофорах:  0 − 0.02 · (−120.75) = 2.415
вес при курьерах:    0 − 0.02 · (−180.75) = 3.615
сборка:              0 − 0.02 · (−43.75) = 0.875

Это один шаг. Дальше то же самое ещё 1999 раз — и вот тут пора писать программу.
 

Программа целиком, без библиотек

Пять пунктов выше переводятся в код почти дословно. Пункты 2 и 3 — это внутренний цикл по заказам, пункт 4 — четыре строки сразу после него, пункт 5 — внешний цикл.



Что стоит заметить в этом коде.

  • Четыре накопителя g1, g2, g3, gb обнуляются на каждом шаге заново. Если забыть их обнулить, производные начнут складываться со старыми, и шаг выйдет длиннее задуманного.
  • Все четыре параметра обновляются после внутреннего цикла, а не внутри него. Градиент считается в одной точке, значит и веса должны меняться все разом. Если переставить обновление внутрь, каждый следующий заказ будет считаться уже по новым весам, и это будет другой алгоритм.
  • Деление на n стоит при обновлении, а не в цикле — так короче, результат тот же.
  • Никаких библиотек не нужно: всё считается обычными циклами и арифметикой.

Вот что она печатает:

шаг    1:  3.800  2.415   3.615  0.875 MSE 377.1338
шаг  500:  3.526  1.899  -0.243  3.514 MSE   0.5103
шаг 1000:  3.321  1.743  -0.343  5.265 MSE   0.1896
шаг 1500:  3.196  1.648  -0.405  6.333 MSE   0.0705
шаг 2000:  3.119  1.590  -0.442  6.984 MSE   0.0262

В каждой строке показаны параметры после очередного обновления и MSE, вычисленная уже с этими параметрами. После первого шага веса совпали с нашим расчётом руками, а ошибка уменьшилась с 505.3750 до 377.1338.

 
Границы учебной программы

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

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

Результат после двух тысяч шагов

  км светофор курьер сборка MSE
получилось 3.1192 1.5903 −0.4418 6.9839 0.026179
истинные веса 3 1.5 −0.5 8 0

Ошибка почти ноль, а веса отличаются от истинных. Увеличить lr нельзя: при 0.03 счёт расходится.

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

Почему спуск застрял: овраг

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

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

Такой рельеф удобно рисовать линиями равной высоты, как на топографической карте: все точки одной линии дают одну и ту же ошибку. Дно — в середине, и чем дальше от него, тем линии выше.



Слева показана функция потерь на исходных признаках. Линии равной ошибки здесь сильно вытянуты: получается узкая длинная долина. Такую форму часто называют оврагом.

Справа — та же задача после стандартизации признаков. Линии стали гораздо ближе к кругам. Почему это помогает, разберём чуть позже. Сначала поймём, чем плох овраг.

Почему в овраге спуск идёт медленно

Представим упрощённый случай: у модели всего два параметра — вес w и свободный член b. Минимум функции потерь находится около точки:

w = 3
b = 8

Но ошибка меняется по этим двум направлениям очень по-разному.

Поперёк оврага функция потерь крутая: небольшое изменение параметра сильно меняет ошибку. Вдоль оврага она пологая: параметр можно изменить заметно, а ошибка изменится совсем немного.

Допустим, в некоторой точке две компоненты градиента равны:

по крутому направлению: 20
по пологому направлению: 1

Возьмём:

lr = 0.02

Тогда величины изменений будут:

20 · 0.02 = 0.40
 1 · 0.02 = 0.02

Получается, что по крутому направлению модель пытается сделать шаг величиной 0.40, а по пологому — всего 0.02.

И здесь возникает главная проблема.

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

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

То есть один и тот же lr должен одновременно решать две разные задачи:

крутое направление:
не шагнуть слишком далеко

пологoe направление:
не ползти слишком медленно

Одним числом хорошо выполнить оба требования трудно.

Откуда берётся зигзаг

На вытянутом овраге большая часть градиента часто направлена поперёк долины, потому что именно там ошибка меняется быстрее всего.

Поэтому вместо спокойного движения вдоль оврага к минимуму получается примерно такой путь:

стенка
   ●
    ↘
      ●
    ↙
   ●
    ↘
      ●
        ↘
          ●  минимум

Спуск перескакивает с одной стороны оврага на другую, а вдоль долины каждый раз продвигается лишь немного.

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

Что происходило в нашей программе

Похожую картину мы уже увидели в числах.

После 2000 шагов модель получила примерно:

вес при километрах: 3.12
свободный член:     6.98

Правильные значения нашей учебной модели:

вес при километрах: 3
свободный член:     8

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

Можно было бы попытаться увеличить lr, чтобы быстрее двигаться по медленному направлению. Но в нашей программе уже при lr = 0.03 обучение начинает расходиться: для более крутого направления такой шаг оказывается слишком большим.

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

Как измерить, насколько овраг вытянут

Можно сравнить самое крутое и самое пологое направления функции потерь около минимума.

Если ошибка во всех направлениях растёт примерно одинаково, линии равной ошибки похожи на круги. Если в одном направлении ошибка растёт намного быстрее, чем в другом, линии превращаются в длинные овалы.

Отношение «самой большой крутизны» к «самой маленькой» называют обусловленностью.

У круглой чаши обусловленность близка к 1. Чем она больше, тем сильнее вытянут овраг и тем сложнее подобрать один общий lr для всех параметров.

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


Спуск по четырём параметрам на сырых данных расходится уже при lr = 0.03, хотя по одному весу спокойно работал при 0.05. Километры идут от 1.5 до 7.5, светофоры от 0 до 6, курьеры от 1 до 8. Признак при свободном члене равен 1. Что ограничивает допустимую скорость обучения?

У круглой чаши она равна единице: куда ни пойди, ошибка растёт одинаково — и тогда один общий lr годится для всех направлений. Чем обусловленность больше, тем сильнее вытянут овраг и тем хуже приходится спуску.

что рассматриваемсырые признакипосле стандартизации
вес при километрах и свободный член105.81
все четыре параметра1896.67.84

По двум параметрам, которые нарисованы на картинке, обусловленность падает со 105.8 ровно до 1 — те самые круги. По всем четырём сразу — с 1896.6 до 7.84: овраг стал гораздо мягче, но круглой чашей не стал.

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


Стандартизация

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

Стандартизация признака (z-score) — вычесть среднее и поделить на стандартное отклонение:

x′ = (x − μ) / s

Здесь x — исходное значение признака, x′ — значение после стандартизации, μ — среднее этого признака по обучающей выборке, s — его стандартное отклонение, мера разброса значений.

Среднее и стандартное отклонение считаем отдельно для каждого столбца признаков. В нашей программе стандартное отклонение вычисляется так:

s = sqrt(среднее((x − μ)²))

Посмотрим на отдельный пример. Пусть среднее расстояние равно 4 км, а стандартное отклонение — 2 км. Тогда:

2 км → (2 − 4) / 2 = −1
4 км → (4 − 4) / 2 =  0
6 км → (6 − 4) / 2 =  1

Ноль теперь означает «расстояние равно среднему», единица — «на одно стандартное отклонение больше среднего», а минус единица — «на одно стандартное отклонение меньше».

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

Меняем только признаки. Фактическое время доставки оставляем в минутах. Значит, прогноз модели тоже по-прежнему выражается в минутах.

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

Что получилось в нашем примере. Сравним два запуска по одному критерию: сколько шагов нужно, чтобы MSE стала меньше 1e-6, то есть одной миллионной. В обоих случаях начинаем с нулевых параметров.

Признаки lr Шагов до MSE < 1e-6
Исходные 0.02 7138
Стандартизированные 0.3 50

После стандартизации удалось взять больший lr и достичь той же точности значительно быстрее. В программе ниже выполним 120 шагов с запасом. К этому моменту MSE будет примерно 1.6 · 10−16 — очень маленькой, но не равной точному нулю.

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

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

Возврат коэффициентов к исходным единицам

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

прогноз = w′₁ · x′₁
        + w′₂ · x′₂
        + w′₃ · x′₃
        + b′

Штрих здесь обозначает стандартизированную шкалу, а не производную.

Эти коэффициенты тоже можно прочитать. Вес w′₁ показывает, на сколько минут изменится прогноз, если расстояние увеличить на одно его стандартное отклонение, оставив остальные признаки неизменными.

Но обычно удобнее спросить: на сколько минут изменится прогноз при увеличении расстояния на один километр? Для этого перепишем модель в исходных единицах.

Сначала разберём один признак. Вернёмся к отдельному примеру со средним 4 км и стандартным отклонением 2 км. Допустим, после обучения получили:

x′ = (x − 4) / 2

прогноз = 6 · x′ + 10

Подставим выражение для x′ и раскроем скобки:

прогноз = 6 · (x − 4) / 2 + 10
        = 3 · (x − 4) + 10
        = 3 · x − 12 + 10
        = 3 · x − 2

Получилась та же модель, только теперь она принимает исходное расстояние. Вес стал равен 3, а свободный член — −2. Обратите внимание: пересчитывать нужно не только вес, но и свободный член, потому что при стандартизации мы вычитали среднее.

Проверим на расстоянии 6 км:

Через стандартизированный признак:
x′ = (6 − 4) / 2 = 1
прогноз = 6 · 1 + 10 = 16

Через исходный признак:
прогноз = 3 · 6 − 2 = 16

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

Для нескольких признаков правило такое же:

w_j = w′_j / s_j

b = b′ − Σ (w′_j · μ_j / s_j)

Здесь j — номер признака; w′_j и b′ — параметры модели на стандартизированных признаках; w_j и b — параметры в исходных единицах. Числа μ_j и s_j — сохранённые среднее и стандартное отклонение этого признака.

Знак Σ означает сумму по всем признакам. В нашей модели их три, поэтому:

b = b′
    − w′₁ · μ₁ / s₁
    − w′₂ · μ₂ / s₂
    − w′₃ · μ₃ / s₃

У свободного члена тоже меняется смысл. В стандартизированной шкале b′ — прогноз для объекта, у которого все признаки равны своим средним значениям: тогда все x′ равны нулю. Поэтому b′ нельзя напрямую называть временем сборки заказа.

И ещё: вес — это коэффициент изменения прогноза при фиксированных остальных признаках, а не универсальная мера важности. Стандартизация делает масштабы сопоставимыми, но сама по себе не превращает веса в рейтинг важности признаков.

Для новых прогнозов есть два равноправных способа. Можно стандартизировать новые признаки сохранёнными μ и s и использовать параметры со штрихами. А можно один раз пересчитать коэффициенты и дальше подставлять исходные признаки. Прогнозы будут одинаковыми с учётом погрешности вычислений.

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

Программа целиком, со стандартизацией

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

Правило обучения остаётся прежним. Но теперь оно работает с признаками в другой шкале, поэтому используем lr = 0.3 и 120 шагов. В конце пересчитаем коэффициенты в исходные единицы, чтобы сравнить их с известными нам значениями 3, 1.5, −0.5 и 8.

В коде переменные w1, w2, w3 и b хранят параметры в стандартизированной шкале. После обратного пересчёта параметры в исходных единицах попадут в v1, v2, v3 и sborka.


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


Вот что печатает программа:

В стандартизированной шкале: 5.8787 2.8498 -1.1456; b' = 21.8750
В исходных единицах:         3.0000 1.5000 -0.5000; b = 8.0000
MSE на обучающей базе: 1.640e-16

Первая строка — параметры модели на стандартизированных признаках. Их тоже можно прочитать: например, вес 5.8787 означает, что при увеличении расстояния на одно его стандартное отклонение прогноз вырастет примерно на 5.88 минуты, если остальные признаки не меняются.

Свободный член b′ = 21.8750 здесь — не время сборки. Это прогноз для заказа, у которого расстояние, число светофоров и число курьеров равны своим средним значениям по обучающей таблице. У такого заказа все стандартизированные признаки равны нулю, поэтому в прогнозе остаётся только b′.

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

прогноз ≈ 3 · км
         + 1.5 · светофоры
         − 0.5 · курьеры
         + 8

Мы почти восстановили те четыре числа, по которым была построена учебная таблица. Почти, а не в точности: программа печатает только четыре знака после точки, поэтому небольшие отличия от 3, 1.5, −0.5 и 8 не видны.

Последняя строка показывает оставшуюся ошибку. Запись 1.640e-16 означает 1.640 · 10−16. Это очень маленькая MSE, но не точный ноль.

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

прогнозы → промахи → градиент → обновление параметров → повтор

Сравним с первой программой. На исходных признаках при lr = 0.02 после 2000 шагов MSE оставалась около 0.0262. После стандартизации при lr = 0.3 уже после 120 шагов она стала примерно 1.6 · 10−16.

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

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


Печать