Расследование проблем числовой неточности

В нейронных сетях, которым задаются бинарная классификация, сигмоидная активация в последнем (выходном) слое и бинарная кроссэнтропия (BCE) в качестве функции потерь, являются стандартными. транспортные расходы. Тем не менее, иногда можно встретить утверждения, что эта конкретная комбинация активации последнего слоя и потери может привести к неточности числовых значений или даже нестабильности. Я хотел убедиться, что у меня есть аргументы, вплоть до цифр, особенно в рамках, которое я использую до сих пор, Keras. Звучит интересно? Понимаете, что это также может иметь отношение к некоторым задачам сегментации изображений или задачам с несколькими классами и метками, а не только с проблемами типа cats_vs_dogs? Тогда, пожалуйста, продолжайте. Если вы заранее хотите освежить в памяти BCE, я хотел бы указать на отличное, подробное объяснение BCE Даниэля Годоя.
1. Определение проблемы
Давайте начнем с анализа реализации BCE Керасом:
Итак, входной аргумент output сначала обрезается, затем преобразуется в логиты, а затем передается в функцию TensorFlow tf.nn.sigmoid_cross_entropy_with_logits. ОК ... опять что за логиты? В математике функция logit является обратной сигмоидной функции, поэтому теоретически logit (sigmoid (x)) = x.

В глубоком обучении логиты обычно и к сожалению означают сырые выходные данные последнего уровня сети классификации, то есть выходные данные уровня до, которые передаются в функцию активации / нормализации. , например сигмовидная. Необработанные выходные данные могут принимать любое значение. Это то, чего ожидает sigmoid_cross_entropy_with_logits, ядро binary_crossentropy Кераса. В Keras, напротив, ожидается, что значения в переменной output представляют вероятности и, следовательно, ограничены [0 1] - вот почему для from_logits по умолчанию установлено значение False. Таким образом, их необходимо преобразовать обратно в исходные значения перед подачей в sigmoid_cross_entropy_with_logits. Повторюсь, мы сначала прогоняем числа через одну функцию (сигмоид), только чтобы преобразовать их обратно с помощью обратной функции (логит). Это кажется окольным. Однако реальная потенциальная проблема заключается в числовой нестабильности, которую это может вызвать взад и вперед, что в крайнем случае приведет к переполнению. Посмотрите на вывод y = logit (sigmoid (x)), когда x имеет тип float32, значение по умолчанию в Keras и, насколько мне известно, в большинстве других фреймворков тоже:

Начиная примерно с x = 14,6 ошибки попадают в диапазон 1%, а после примерно x = 16,6 игра заканчивается из-за деления на ноль. Деление на ноль происходит, когда знаменатель в сигмоиде равен 1, а знаменатель в логите затем равен нулю. Мы могли бы оценить предел x с помощью np.log(np.finfo(np.float32).eps), но я нашел числовые зигзаги интересными и достойными внимания. В любом случае, именно поэтому значения входной переменной output в функции Keras должны быть и фактически обрезаны. Итак, чтобы с самого начала развеять одно понятие:
binary_crossentropy Кераса при подаче входных данных, полученных в результате активации сигмоида, не приведет к переполнению или недостаточному количеству чисел.
Однако результатом отсечения является сглаживание функции потерь на границах. Чтобы визуализировать это, давайте
- скопируйте изящный трюк создания минималистичных сетей с вручную установленными весами, и
- пропускать отдельные входы (образцы, размер пакета 1) через сети.
Таким образом, мы можем сравнивать значения BCE, вычисленные из активации сигмоида в Keras, со значениями, вычисленными из необработанных выходных данных в TensorFlow. Вот первая модель, использующая сигмовидную активацию и стандартную BCE Кераса:
Модель без активации сигмоида, с использованием настраиваемой функции потерь, которая вставляет значения непосредственно в sigmoid_cross_entropy_with_logits:
Итак, если мы оценим модели в широком диапазоне скалярных входных данных x, установив метку (y) на 1, мы можем сравнить созданные моделью BCE друг с другом, а также со значениями, полученными наивной реализацией BCE, вычисленной с помощью высокоточный поплавок. Еще раз обратите внимание, что здесь мы вычисляем BCE на основе отдельных образцов, чтобы выявить различия между методами.

Интересно! Кривая, рассчитанная на основе необработанных значений с использованием sigmoid_cross_entropy_with_logits TensorFlow, является сглаженной во всем диапазоне протестированных значений x, тогда как кривая, рассчитанная на основе значений, преобразованных сигмоидальным путем, с binary_crossentropyflattens Кераса в обоих направлениях (как и прогнозировалось). При больших положительных значениях x перед достижением предела, вызванного отсечением, сигмовидная кривая имеет ступенчатый вид. Наиболее примечательным может быть то, что происходит на левой границе, которая лучше всего оценивается на линейной шкале (рис. 3, в центре): чем отрицательнее исходное значение, тем серьезнее занижение его значения BCE из-за отсечения. Наконец, даже с float128 мы не продвинемся очень далеко, если получим значения BCE из входных данных с сигмоидальным преобразованием (серая кривая на крайнем левом графике).
Поскольку функция потерь является центральным элементом обучения, это означает, что модель, использующая сигмоид + BCE последнего слоя, не может различать выборки, предсказанный класс которых находится либо в крайнем соответствии, либо в крайнем несоответствии с их метками. Итак, теоретически это правда, что у использования сигмоида + BCE есть недостаток. Однако имеет ли это значение на практике? В конце концов, мы говорим о крайних случаях. И прежде чем мы ответим на этот вопрос, следует задать более простой вопрос: достигают ли необработанные выходные данные последнего слоя таких экстремальных значений на практике? Давайте посмотрим.
2. Проверка исходных выходных значений отдельных образцов в сетях двоичной классификации.
Я обучил три разные сети задаче классификации собак и кошек, используя подмножество набора данных о соревнованиях Kaggle 2013 года (2000 обучающих изображений, 1000 для проверки; следуя примеру Ф. Шолле (Deep Learning with Python . Manning Publications Co, Shelter Island, New York, 2017.)). Да, опять же, кошки и собаки, для удобства и сосредоточения внимания на рассматриваемой проблеме.
Цифры и числа ниже относятся к простой, созданной вручную свертке: четыре пары двумерных слоев свертки / максимального объединения, за которыми следуют один выпадающий слой и два плотных слоя, все с повторной активацией. Последний одноэлементный выходной слой не был активирован, и в качестве функции потерь я использовал вышеупомянутую оболочку Keras для sigmoid_cross_entropy_with_logits TensorFlow.
Во-первых, давайте выясним, действительно ли отдельные изображения могут приводить к экстремальным необработанным значениям выходного слоя. После обучения я прогнал по сети те же изображения, что и для обучения, и получил их необработанные выходные данные из последнего слоя. Кроме того, я вычислил выходной сигнал с преобразованием сигмоида, а также значения BCE, полученные из обоих выходов. Это то, что я получил после тренировки в течение восьми эпох, поэтому при относительно небольшом объеме обучения:

Очевидно, что на начальном этапе обучения мы находимся за пределами опасной зоны; необработанные выходные значения последнего слоя ограничены ca [-3 8] в этом примере, а значения BCE, вычисленные из исходных и сигмоидальных выходов, идентичны. Также приятно видеть сильный эффект «раздавливания» сигмовидной кишки (рис. 4, в центре).
Как выглядит картина, когда сеть полностью обучена (здесь определено после того, как не было продемонстрировано снижение потерь в течение 15 последовательных эпох)?

Ага, как и ожидалось, мы видим гораздо более четкое разделение между классами. И небольшое количество изображений действительно привело к экстремальным значениям логита, которые попадают в диапазон отсечения. Давайте сосредоточимся на классе 1 (собаки; оранжевый цвет на рисунках); аргумент работает аналогично для другого класса. Ни один из образцов не дает более отрицательных исходных значений, чем ок. -4, так что снова все в порядке. Однако определенное количество образцов - самые собачьи собаки - достигают необработанных выходных значений, превышающих примерно 16. Соответственно, связанная BCE, вычисленная с помощью сигмоидальной + двоичной_кросцентропии Кераса, обрезается на уровне прибл. 10⁻⁷ (рис. 5, справа; см. Также рис. 3). Это действительно небольшая величина. Могли бы мы ожидать, что обучение будет происходить систематически по-другому, если бы значения BCE для собачьих собак и кошачьих были меньше (и индивидуально различались) при вычислении без ограничений, вызванных вырезанием? В частности, если мы используем разумные размеры пакетов, выборки с промежуточными или низкими значениями исходных выходных данных будут преобладать в потерях. Рисунок 6 иллюстрирует это для тех же данных, что и выше, с размером пакета 4, который все еще действительно на низкой стороне.

Результаты были качественно аналогичными для сетей на основе VGG16 и Inception V3, предварительно обученных на наборе данных изображений (обученных без точной настройки сверточных частей).
3. Выводы
Прежде всего, давайте повторим, что опасения недостаточного или переполнения числа из-за комбинации сигмовидной активации на последнем слое и BCE в качестве функции потерь неоправданы - в Keras с использованием бэкэнда TensorFlow. Я не знаком с другими фреймворками (пока), но был бы очень удивлен, если бы в них не было подобных мер предосторожности.
Основываясь на «экспериментах» выше с почтенным набором данных cats_vs_dogs, кажется, что сигмовидный + BCE также хорош с точки зрения точности. В частности, если вы используете разумный размер пакета и правильно масштабированные данные, не имеет значения, как вычисляется BCE. Однако это всего лишь один набор данных и на них протестировано очень мало моделей.
Итак, мое предварительное резюме: если
- вы знаете или подозреваете, что необработанные выходные данные многих ваших сэмплов на последнем слое достигают экстремальных значений, и
- размер вашей партии очень мал и / или
- вы хотите исключить коммерческую неточность как возможную (если маловероятную) причину проблемы,
вычисление BCE из необработанных выходных данных не повредит. В противном случае просто используйте сигмоид + BCE.
Комментарии, предложения, опыт работы с другими фреймворками? Рад слышать об этом.