Эта часть будет посвящена теоретическому обзору проблем и их решений в контексте задачи распознавания эмоций. Несмотря на то, что многие из перечисленных проблем уже давно изучены, а методы борьбы с ними реализованы в существующих фреймворках, знать хотя бы об их существовании будет полезно.
В этой части мы коротко поговорим о данных, о работе сверточных нейросетей и о глобальных параметрах. От том что такое СГС и почему нельзя решать задачу в виде линейного уравнения. Затронем тему оптимизаторов и ответим на вопрос почему нельзя просто использовать обычный градиентный спуск. В общем обо всех деталях коротко и структурно.
Внимательного чтения!
Задача распознавания эмоций. Часть 1. Введение: https://habr.com/ru/articles/824744/
Для начала давайте разберемся, какие общие требования существуют к данным:
Репрезентативность - данные должны быть разнообразными и отражать генеральную совокупность. В нашем случае это человечество, а значит изображения должны содержать лица разных рас, возраста и обоих полов. Это необходимо для того чтобы модель реагировала на ключевые признаки, свойственные для проявления эмоций, а не ширину носа или форму скул у отдельных людей;
Объем - данные должны быть достаточно объемными, чтобы модель смогла уловить ключевые признаки. Больше данных - больше вариаций, и, как следствие, лучшая обобщающая способность;
Баланс классов - данные не должны содержать перекосов по классам. Об этом речь пойдет далее.
Малый объем данных.
Это серьезная проблема в контексте нашей задачи, которую трудно решить быстрыми способами. Все дело в том, что, к примеру, аугментация не может повысить разнообразие данных, так как перевороты и затемнения не меняют пропорции лиц и не создают новых вариаций. Выход только один - увели чить датасет.
Сильный перекос в данных.
Модель склонна выбирать класс, который лучше представлен в данных. При этом, если данные содержат множество микро-классов, то модель начнет в них путаться, ибо она не сможет уловить их ключевые отличия. Как известно, любой объект можно определить двумя путями, через описание (что такое круг) и исключение (круг - это не квадрат и не треугольник).
Для хорошей классификации нужны устойчивые признаки всех видов объектов, которые не будут сливаться между собой.
Выбор метода борьбы с дисбалансом напрямую зависит от его уровня:
Небольшой перекос не требует устранения;
Значительный перекос может быть исправлен с помощью корректирующих весов (мы разберем это подробнее в 3 части);
При наличии большого количества микро-классов можно объединить их в один, заранее согласившись на некую неустранимую ошибку. При этом рост итоговой ошибки будет незначительным по сравнению с моделью обученной на сильно разбалансированных данных.
Особые случаи
Однако бывают ситуации, когда сама задача предполагает перекос классов. Например: диагностика редких заболеваний, прогноз работы критически важных деталей, а также поиск мошеннических транзакций, где соотношения классов может достигать многотысячных значений!
То есть, мы не можем взять шизофрению, рак и умственную отсталость и объединить их в один класс с припиской "их все равно мало". Это не просто неэтично, это обнулит весь смысл задачи.
В этих случаях можно воспользоваться аугментацией малых классов и/или применить undersampling - случайное удаление данных из крупных классов. Также применяется обучение в цикле, где крупные классы разбиваются на части, а малые повторяются.
Лучшим методом на сегодня являются алгоритмы Генерации Синтетических Данных. Это самый сложный и современный подход, предполагающий применение генеративных моделей GAN. Он используется крупными корпорациями, исчерпавшими доступные информационные ресурсы, созданные человеком. Если коротко, то специальные генеративные сети создают массивы данных, на которых дообучаются другие тестовые модели. Если качество растет, то данные признаются годными и сохраняются для дальнейшего использования и так до бесконечности.
Современные модели предполагают использование глубоких сверточных сетей (или трансформеров) и состоят из следующих блоков:
1. Препроцессинг - слои нормализации и аугментации;
2. Основная сеть - сверточные и пуллинг слои;
3. Выравнивание - может быть осуществлено различными методами (обычное выравнивание, выравнивание со сверткой, максимизация, усреднение);
3*. LSTM слой (для видео) - данный слой получает на вход 3-х мерный тензор векторов изображений вида [Размер батча, Кол. кадров, Кол. каналов] и ищет зависимости в них, выдавая итоговую последовательность;
4. Выходной слой - полносвязный слой-классификатор.
CNN предполагают извлечение признаков изображения с помощью сверточных слоев, которые сохраняют пространственные отношения между пикселями. Далее, в качестве памятки, перед разбором проблем, будет коротко рассмотрена работа сверточной сети.
Ядро свертки последовательно проходит по каждому каналу изображения с заданным шагом и как бы проецирует входное изображение на выходную карту признаков.
Все пиксели попавшие в окно свертки умножаются на ее параметры, а затем складываются (конволюция). Выходное число записывается в соответствующее место выходной карты.
Так как свертка не может зайти за край карты, выходная карта будет меньше предыдущей. Этот эффект можно устранить с помощью специального параметра padding=’same’, но делать это следует только в случае явной необходимости.
Свертка проходит по всем каналам входного изображения и складывает их. Операция повторяется для каждой свертки. Итоговое количество выходных карт = количеству фильтров в слое.
Существуют и другие версии сверточных слоев. Например, в слое depthwise каждая свертка работает только в одном канале, а количество сверток = количеству входных каналов. В этом случае картам все равно требуется перемешивание информации в канальном измерении с помощью классического сверточного слоя (обычно с размером ядра 1х1).
Сверточные слои имеют довольно большое количество параметров:
Kernel size - размер ядра свертки;
Stride - размер шага в пикселях с которым будет идти свертка. С помощью него можно уменьшать пространственные размеры карт признаков, не прибегая к pooling-слою;
Dilatation - ширина фильтра свертки. То есть свертка может быть не только плотной, а иметь некое расстояние между детекторами;
Padding - следует ли восстанавливать исходный размер карт признаков.
Полносвязные слои получают на вход данные (входной слой) и преобразуют их до тех пор, пока выборка не станет линейно разделимой. После чего данные подаются в выходной слой-классификатор.
Базовым элементом такой сети является искусственный нейрон.
На практике нейрон представляет из себя линейное уравнение со множеством переменных, то есть сумму входных данных от X1 до Xn с весовыми коэффициентами + bias - сдвиг. Все это обернуто в функцию активации (о ней далее). Итоговый результат подается на вход другим нейронам.
Одним из главных атрибутов нейросетей является функция активации.
Так как линейное уравнение (далее ЛУ) от ЛУ - это ЛУ, то сколько бы в сети не было слоев, она будет эквивалентна одноуровневой. Для того чтобы сеть могла модифицировать данные, каждый нейрон (слой) должен проходить функцию активации.
Самыми распространенными функциями являются: ReLU (и ее версии), Sigmoid, Softmax, Tanh. Они применяются в зависимости от задачи.
Нейроны выходного слоя проводят итоговое взаимоисключающее разделение данных по методу один против всех. То есть каждый нейрон отделяет свой класс от всех остальных. Их предсказания записываются в вектор с фиксированной длинной равной количеству классов.
На практике в большинстве моделей используется всего один полносвязный слой. Этого хватает для разделения классов по итоговому вектору признаков.
Теперь, после краткого обзора работы сверточных сетей, поговорим о проблемах связанных с ними и их решениях.
Переобучение является одной из фундаментальных проблем ML. Модель как бы идеально подстраивается под обучающую выборку вместо того чтобы уловить основные признаки. Таким образом ее обобщающая способность снижается, а вместе с ней и качество на тестовых данных.
На графике изображена некая результирующая функция обученной модели.
Первая грамотно уловила суть и смогла отлично аппроксимировать облако точек. Вторая же слишком сильно подстроилась под обучающую выборку и больше напоминает полином.
Нужно отметить, что данное изображение несколько абстрактно, так как в тех же нейронных сетях используются линейные функции, которые не могут никуда подстраиваться. На самом деле мы модифицируем сами данные в слоях сети до тех пор, пока выборка не станет линейно разделимой. Данные же графики используются просто для наглядности.
Переобучению способствует излишняя глубина модели, недостаток объема и разнообразия данных, отсутствие регуляризации, излишние эпохи и т.д.
Для борьбы с переобучением существует множество методов:
Прореживание - отключение части нейронов в случайном порядке на каждом шаге. В следующей части, мы обязательно разберем это на практике;
Аугментация - генерация данных на основе существующих;
Регуляризация - ограничение/штраф весовых коэффициентов модели;
Сглаживание меток - выдача штрафов за излишнюю уверенность модели при классификации (для бинарной классификации);
Ранняя остановка обучения - полезна при признаках переобучения;
Стекинг - объединение прогнозов разных моделей.
Наряду со всеми, одним из самых важных методов является регуляризация.
Чтобы понять его на интуитивном уровне, давайте рассмотрим шуточную задачу, где нужно определить линию разделения поля по координатам игроков.
На изображении видно, что один из игроков попал на половину соперников и находится гораздо дальше, чем игроки этого поля. В следствии этого, вся линия как бы перекосилась, а ошибка выросла. В качестве борьбы с подобным выбросом, кроме прочего, мы можем применить логарифмирование, тем самым уменьшив соотношения между расстояниями игроков, из-за чего линия будет более стабильна.
Во взрослых сетях есть 2 основных вида регуляризации - L1 и L2. Они представляют из себя штраф равный сумме весов с некоторым коэффициентом, называемым силой регуляризации.
L1 - это сильная регуляризация, которая способна даже обнулять веса. Она поможет, когда есть избыточное число признаков, но лишь некоторые из них полезны.
L2 - это штраф равный сумме квадратов весов. Он более щадящий и делает модель устойчивой к шумам и выбросам.
На деле регуляризация уменьшает веса сети и, если так можно выразиться, делает график нейрона более пологим.
С чем же это связано?
Эмпирически было замечено, что если веса большие, то скорее всего модель ошибается. То есть, уменьшив ее веса, мы в худшем случае слегка потеряем в качестве, но наиболее вероятно улучшим качество и обобщающую способность модели.
Существующим методом вычисления градиента в узлах сети, является обратный проход. Он позволяет не тратить огромные ресурсы на вычисления градиента для каждого состояния сети. Вместо этого градиент передается по цепочке от последнего слоя к начальным.
В нем мы как бы разворачиваем нашу сеть словно луковицу, где значения из последующих узлов используются для вычисления градиента в предшествующих по формуле производной сложной функции.
Таким образом, чтобы найти градиент в узлах начальных слоев нам потребуется перемножить большое количество значений, каждое из которых обычно меньше 1. То есть, пока градиент попадет в начальные слои, он "растеряет" свое значение.
Представим, что порядок некого веса в начальном узле = 10е-3, а итоговое значение градиента для его корректировки = 10e-15, что крайне мало. Это означает, что он не сможет ощутимо его изменить, и веса в этих слоях останутся близким к тем, что были получены при случайной инициализации. А значит данные слои совсем не обучатся!
Существующим методом решения проблемы затухающего градиента является проброс значений из предыдущих слоев, минуя один или несколько последующих, как если бы этих слоев не существовало. Таким образом, мы складываем два градиента, пришедших сразу из двух мест, в следствии чего итоговое значение градиента значительно возрастает.
Достоинство этого метода в том, что добавочное значение не взялось с потолка, а также было получено в процессе обучения.
Кроме того, нам может помочь применение правильной функции активации.
Функция ReLU представляет из себя простейшую нелинейную функцию и обладает следующими достоинствами:
* Наименьшей ресурсоемкостью;
* Высокой скорость вычисления;
* Не вносит вклад в затухание (и возрастание) градиента, благодаря своей производной.
Иногда при инициализации модели некоторые из случайно подобранных весов оказываются крайне неудачными. Нейроны, получившие их, рискуют никогда не активироваться, когда как другие могут быть активны постоянно.
Кроме того, функция активации Relu также может усиливать этот эффект из-за своей способности обнулять значения меньше нуля.
Для решения этой проблемы существует механизм отдыха, основанный на реальной биологии. Нейрон, который был активен, на какое-то время уходит на отдых, давая возможность другим проявить себя.
LeakyReLU и другие.
Также существуют улучшенные версии функции ReLU, которые работают и с отрицательными значениями.
Например, функция LeakyReLU отличается тем, что имеет некоторый коэффициент при Х < 0 (он может варьироваться 0.01-0.05) и не обнуляет отрицательные значения полностью.
Глобальные параметры - это параметры (оптимизаторы, функции, переменные и прочие настройки), что мы передаем еще до начала обучения. Неточный или неверный подбор данных параметров может оказать крайне негативное влияние на качество и испортить самую совершенную модель. Поэтому так важно уделить им особое внимание.
Для обучения моделей принято использовать метод градиентного спуска.
Градиент - это вектор, значениями которого являются частные производные функции (функции потерь в нашем случае). Он указывает в сторону ее наискорейшего возрастания.
Используя направление антиградиента, вычисляемое на каждом шаге при обратном проходе, можно постепенно корректировать веса, снижая итоговую ошибку.
Данный процесс и называется обучением модели.
Прежде чем двигаться дальше, нужно пояснить, почему в ML не используются подход линейных уравнений. Разберем его на простом примере.
Допустим у нас есть матрица данных X и вектор целевых признаков y. Тогда модель можно представить в виде линейного уравнения X * w = y, где w - это вектор весов (наше искомое) [1].
Для решения мы должны умножить обе части уравнения на обратную матрицу X. [2] В левой части у нас получится единичная матрица на вектор весов, а в правой обратная на вектор целевых признаков [4].
Для поиска обратной матрицы нужно найти союзную матрицу, транспонировать ее и разделить на определитель [3]. В этом месте скрывается первая проблема: если определитель = 0, то в результате деления на него получается неопределенность, и решений у уравнения не будет.
Кроме того, в подавляющем большинстве случаев матрица данных будет иметь прямоугольную форму, так как строк много больше чем столбцов. А значит для поиска решений нам потребуется псевдо квадратная матрица, получить которую можно умножив ее транспонированный вариант на обычный (помним, что закон перестановок в ЛА не работает как в обычной алгебре). Иными словами, мы берем матрицу в N строк и превращаем ее в NxN, а это квадратичная сложность! Она не страшна, если N - это небольшое число, но что если строк у нас миллионы? Именно поэтому подобных вещей стараются избегать.
Кроме того в СГС мы подаем данные частями, а здесь вынуждены подать все сразу. Проще говоря, причин предостаточно.
Еще раз все подытожим:
Нестабильность. Для поиска решения ЛУ нужно найти обратную матрицу. Если определитель = 0, то уравнение не имеет решений;
Сложность. В подавляющем большинстве случаев матрица данных имеет прямоугольную форму. Для решения ЛУ нужна псевдо квадратная матрица, которая усложняет задачу до О^2(n), что очень чувствительно при большом количестве строк;
Ресурсоемкость. Требуется работать со всеми данными сразу;
Отсутствие промежуточных результатов. В случае любой остановки вычислений, результата не будет.
Еще одни из важных аспектов при тренировке модели, является функция потерь (loss). Это функция позволяющая рассчитать текущую ошибку модели. Она помогает понять где мы находимся в нашем поиске и куда движемся.
Для работы со множеством категорий используются loss Категориальная кросс-энтропия.
Как это работает?
На вход в функцию подаются 2 вектора, с истинными значениями и предсказанными моделью. Например для модели на 4 класса они могут выглядеть так: Ptrue = [0 1 0 0] и Ppred = [0.1 0.75 0.022 0.128]. Далее их попарные произведения Ptrue * Ln(Ppred) от 1 до К, где К - это количество классов, суммируются. Так как вектор целевых значений Ptrue разряженный (лишь одно значение = 1 и все остальные равны нулю) нас будет интересовать только оно.
В лучшем случае метки совпадут: Ptrue = 1 и Ppred -> 1 (стремиться к 1). Логарифм от числа, стремящегося к 1, будет стремиться к нулю. Далее 1 * 0 = 0 и итоговая ошибка будет минимальной.
Обратный случай, если классификатор ошибся и целевой класс имеет предсказание стремящееся к 0, то есть Ptrue = 1 и Ppred -> 0. Логарифм от числа, которое стремиться к 0, будет очень большим отрицательным числом. Далее складываем с остальными нулями (там где Ptrue = 0) и получаем огромную ошибку.
В бинарной кросс-энтропии все аналогично, за тем исключением, что обычно мы ставим в выходной слой только один нейрон, так как значение второго комплиментарно и его можно легко вычислить отняв Ptrue из 1. Теоретически, если в последнем слое поставить 2 нейрона, то можно использовать и первую формулу, но делать так незачем :).
Для того чтобы грамотно подбирать глобальные параметры, необходимо знать о проблемах, которые они призваны устранять или могут вызывать. Забегая наперед, большинство из них связаны со скоростью обучения (learning rate) и градиентом. Однако, не все так тривиально, и для того чтобы влиять на скорость извне всего одной переменной, потребовалось создание сложных алгоритмов, борющихся с недостатками СГС внутри самой сети.
Одной из фундаментальных проблем градиентного спуска, является застревание в локальном минимуме. В классическом методе есть большой риск попасть в него и застрять, то есть досрочно прекратить обучение модели. К сожалению, используя СГС нельзя заранее определить до каких пределов способна опуститься ошибка.
Проблема застревания связана с производной функции. Мы знаем, что она равна нулю в точках экстремума, которой и является локальный минимум. А значит при приближении к нему, градиент будет стремиться к нулю и более не сможет адекватно корректировать веса сети, так и не достигнув лучших результатов.
Кроме всего, на протяжении обучения градиент может колебаться в очень широких диапазонах от шага к шагу. То есть, в один и тот же слой он может прийти на порядки большим, чем на предыдущем шаге. Не трудно догадаться, как это скажется на корректировке весов...
Метод импульсов - это метод сглаживания градиентов с помощью скользящего среднего, который позволяет стабилизировать значения градиентов, опираясь на предыдущие. Именно он призван решить проблемы, описанные до этого.
С помощью скользящего среднего, мы берем значения градиентов на нескольких последних шагах и усредняем их. Это значение с некоторым коэффициентом складывается с градиентом, вычисленным на последнем шаге [2].
Таким образом, каждый вес будет скорректирован не просто градиентом, а скользящим средним [3], которое движется по ходу обучения, где последнее значение вносит наибольший вклад (но не всегда решающий).
Есть формула, по которой можно примерно определить, сколько последних значений будет учтено в зависимости от параметра гамма [4]. На самом деле ЭСС учитывает все значения, но лишь N последних вносят ощутимый вклад.
Таким образом, данный метод приводит к стабилизации значения градиента от шага к шагу и, кроме прочего, придает ему некую инерцию. Теперь, в случае попадания в локальный минимум, скорость сохраняется, и мы на протяжении нескольких шагов имеем высокие шансы преодолеть его и продолжить обучение в нормальном режиме.
Кроме колебаний градиента от шага к шагу, он может сильно колебаться и в узлах, при вычислении частных производных. Это приводит к тому, что одни веса активно обновляются, когда как другие остаются почти неизменными. При этом увеличение самого градиента или общей скорости не способно изменить соотношения итоговых градиентов.
Для решения этой проблемы существует метод RMSProp, который нормализует скорость подстройки весов внутри каждого нейрона. Здесь мы также находим скользящее среднее, но только от квадратов градиентов [3] для того чтобы избавиться от знака. После этого, каждый градиент делиться на корень из этого значения [4].
Если корень из G > 1, значение градиента уменьшается, если < 1, то деление на него увеличивает итоговый результат. Таким образом и производится стабилизация значений конечных градиентов.
Параметр ε - это некое число близкое к нулю, он нужен чтобы гарантированно избежать деления на 0 [4].
Вот мы и пришли к оптимизаторам - алгоритмам, которые помогают улучшить стандартный стохастический градиентный спуск.
Один из самых популярных оптимизаторов AdaM является комбинацией вышеперечисленных методов [3]. Он стабилизирует значение градиента от шага к шагу [1], а так же внутри узлов при вычислении частных производных [2], для того чтобы все веса корректно обновлялись. Использование оптимизаторов помогает компенсировать большинство недостатков СГС, благодаря чему этот метод и является основным.
[4] В этой строке перечислены дефолтные значения для параметров оптимизатора.
Грамотный подбор скорости также может помочь в обучении модели. Но кроме этого, скорость может изменяться и во время обучения.
Планировщики скорости, например такие как экспоненциальное снижение (ExponentialDecay) или кастомные планировщики LearningRateSchedulerr и другие, помогут нам обучаться с высокой скоростью в начале и снизить ее к концу обучения, когда нужное направление уже найдено и остается лишь осторожно отъюстировать веса.
Мы обязательно задействуем их на практике в последней части статьи.
Понимание истоков области почти всегда помогает находить лучшие решения и заранее избегать проблем. Известно, что знание особенностей и истории языка программирования (например то, что Python написан на С) может улучшить эффективность кода. Знание теории машинного обучения также поможет лучше понять, почему используются те или иные методы/параметры и самому сделать выбор в сторону самых оптимальных.
Написание статьи довольно кропотливый труд. Буду рад услышать конструктивную критику или указание на неточности/ошибки.
Если есть вопросы по материалу, то задавайте их в комментариях.
Конец второй части.
Задача распознавания эмоций. Часть 1. Введение: https://habr.com/ru/articles/824744/