Обзор статьи: общая и адаптивная надежная функция потерь

Недавно я наткнулся на удивительную статью, представленную в CVPR 2019 Джоном Барроном о разработке надежной и адаптивной функции потерь для задач машинного обучения. Этот пост представляет собой обзор этой статьи вместе с некоторыми необходимыми концепциями, и он также будет содержать реализацию функции потерь для простой задачи регрессии.

Проблема с выбросами и устойчивыми потерями:

Рассмотрим одну из наиболее часто используемых ошибок в задачах машинного обучения - среднеквадратичную ошибку (MSE). Как вы знаете, он имеет форму (y-x) ². Одной из ключевых характеристик MSE является его высокая чувствительность к большим ошибкам по сравнению с маленькими. Модель, обученная с помощью MSE, будет смещена в сторону уменьшения наибольших ошибок. Например, одна ошибка в 3 единицы будет иметь такую ​​же важность для 9 ошибок из 1 единицы.

Я создал пример, используя Scikit-Learn, чтобы продемонстрировать, как соответствие варьируется в простом наборе данных с учетом и без влияния выбросов.

Как вы можете видеть, линия соответствия, включающая выбросы, сильно зависит от выбросов, но проблема оптимизации должна требовать, чтобы на модель больше влияли выбросы. На этом этапе вы уже можете думать о средней абсолютной ошибке (MAE) как о лучшем выборе, чем MSE, из-за меньшей чувствительности к большим ошибкам. Существуют различные типы устойчивых потерь (например, MAE), и для конкретной проблемы нам может потребоваться протестировать различные потери. Разве не было бы замечательно тестировать различные функции потерь на лету при обучении сети? Основная идея статьи состоит в том, чтобы представить обобщенную функцию потерь, в которой устойчивость функции потерь может варьироваться, и этот гиперпараметр может быть обучен во время обучения сети для повышения производительности. На это уходит меньше времени, чем, скажем, на поиск наилучшего убытка путем перекрестной проверки поиска по сетке. Давайте начнем с определения ниже -

Устойчивые и адаптивные потери: общая форма:

Общая форма устойчивых и адаптивных потерь следующая:

α контролирует надежность функции потерь. c можно рассматривать как параметр масштаба, который управляет размером чаши около x = 0. Поскольку α действует как гиперпараметр, мы можем видеть, что для разных значений α функция потерь принимает знакомые формы. Посмотрим ниже -

Функция потерь не определена при α = 0 и 2, но, взяв предел, мы можем сделать приближения. От α = 2 до α = 1 потери плавно переходят от потерь L2 к потерям L1. Для различных значений α мы можем построить функцию потерь, чтобы увидеть, как она себя ведет (рис. 2).

Мы также можем потратить некоторое время на первую производную этой функции потерь, потому что производная необходима для оптимизации на основе градиента. Для различных значений α производные по x показаны ниже. На рисунке 2 я также построил производные вместе с функцией потерь для различных α.

Поведение адаптивной потери и ее производная:

Рисунок ниже очень важен для понимания поведения этой функции потерь и ее производной. Для графиков ниже я установил параметр масштаба c на 1,1. Когда x = 6,6, мы можем рассматривать это как x = 6 × c. Мы можем сделать следующие выводы об убытке и его производной:

  1. Функция потерь гладкая для x, α и c ›0 и, таким образом, подходит для оптимизации на основе градиента.
  2. Потери в начале координат всегда равны нулю и монотонно возрастают при | x | ›0. Монотонный характер убытка также можно сравнить с ведением журнала убытка.
  3. Потери также монотонно увеличиваются с увеличением α. Это свойство важно для устойчивой природы функции потерь, потому что мы можем начать с более высокого значения α, а затем постепенно (плавно) уменьшать во время оптимизации, чтобы обеспечить надежную оценку , избегая локальных минимумов .
  4. Мы видим, что при | x | ‹c производные почти линейны для разных значений α. Это означает, что производные пропорциональны величине остатка, когда они невелики.
  5. Для α = 2 производная пропорциональна величине остатка. В общем, это свойство потерь MSE (L2).
  6. Для α = 1 (дает L1 Loss) мы видим, что величина производной насыщается до постоянного значения (точно 1 / c) за пределами | x | ›c. Это означает, что эффект остатков никогда не превышает фиксированной суммы.
  7. Для α ‹1 величина производной уменьшается как | x |› c. Это означает, что увеличение остатка меньше влияет на градиент, поэтому выбросы будут иметь меньший эффект во время градиентного спуска.

Я также построил под поверхностью графики устойчивых потерь и их производной для различных значений α.

Реализация надежной потери: Pytorch и Google Colab:

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

Вместо того, чтобы клонировать репозиторий и работать с ним, мы можем установить его локально, используя pip in Colab.

!pip install git+https://github.com/jonbarron/robust_loss_pytorch
import robust_loss_pytorch 

Мы создаем простой линейный набор данных, включающий нормально распределенный шум, а также выбросы. Поскольку в библиотеке используется pytorch, мы конвертируем массивы чисел x, y в тензоры с помощью torch.

import numpy as np
import torch 
scale_true = 0.7
shift_true = 0.15
x = np.random.uniform(size=n)
y = scale_true * x + shift_true
y = y + np.random.normal(scale=0.025, size=n) # add noise 
flip_mask = np.random.uniform(size=n) > 0.9 
y = np.where(flip_mask, 0.05 + 0.4 * (1. — np.sign(y — 0.5)), y) 
# include outliers
x = torch.Tensor(x)
y = torch.Tensor(y)

Затем мы определяем класс линейной регрессии, используя модули pytorch, как показано ниже:

class RegressionModel(torch.nn.Module):
   def __init__(self):
      super(RegressionModel, self).__init__()
      self.linear = torch.nn.Linear(1, 1) 
      ## applies the linear transformation.
   def forward(self, x):
      return self.linear(x[:,None])[:,0] # returns the forward pass

Затем мы подгоняем модель линейной регрессии к нашим данным, но сначала используется общая форма функции потерь. Здесь мы используем фиксированное значение α (α = 2.0), и оно остается постоянным на протяжении всей процедуры оптимизации. Как мы видели для α = 2.0, функция потерь воспроизводит потерю L2, и это, как мы знаем, не оптимально для проблем, включая выбросы. Для оптимизации мы используем оптимизатор Adam со скоростью обучения 0,01.

regression = RegressionModel()
params = regression.parameters()
optimizer = torch.optim.Adam(params, lr = 0.01)
for epoch in range(2000):
   y_i = regression(x)
   # Use general loss to compute MSE, fixed alpha, fixed scale.
   loss = torch.mean(robust_loss_pytorch.general.lossfun(
     y_i — y, alpha=torch.Tensor([2.]), scale=torch.Tensor([0.1])))
   optimizer.zero_grad()
   loss.backward()
   optimizer.step()

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

Общая форма функции потерь не позволяет изменять α, и поэтому мы должны точно настроить параметр α вручную или путем поиска по сетке. Кроме того, как видно из рисунка выше, на соответствие влияют выбросы, потому что мы использовали потери L2. Это общий сценарий, но что произойдет, если мы будем использовать адаптивную версию функции потерь? Мы вызываем модуль адаптивных потерь и просто инициализируем α и позволяем ему адаптироваться на каждой итерации.

regression = RegressionModel()
adaptive = robust_loss_pytorch.adaptive.AdaptiveLossFunction(
           num_dims = 1, float_dtype=np.float32)
params = list(regression.parameters()) + list(adaptive.parameters())
optimizer = torch.optim.Adam(params, lr = 0.01)
for epoch in range(2000):
   y_i = regression(x)
   loss = torch.mean(adaptive.lossfun((y_i — y)[:,None]))
   # (y_i - y)[:, None] # numpy array or tensor
   optimizer.zero_grad()
   loss.backward()
   optimizer.step()

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

Обсуждение:

Мы видели, как устойчивые потери, включая гиперпараметр α, можно использовать для нахождения наилучшей функции потерь на лету. В документе также показано, как устойчивость функции потерь с α в качестве непрерывного гиперпараметра может быть введена в классические алгоритмы компьютерного зрения. Примеры реализации адаптивных потерь для оценок глубины с помощью вариационного автоэнкодера и монокуляра показаны в документе, и эти коды также доступны в Jon’s GitHub. Однако больше всего меня увлекла мотивация и пошаговый вывод функции потерь, как описано в статье. Легко читать, предлагаю взглянуть на бумагу!

Оставайтесь сильными и радуйтесь !!

Использованная литература:

[1] Общая и адаптивная надежная функция потерь; Дж. Бэррон, Google Research.

[2] Robust-Loss: пример линейной регрессии; GitHub Джона Бэррона.

[3] Поверхностный график устойчивых потерь и анимации: GitHub Link.