[Перевод] Глубокие нейронные сети: 33 года назад и 33 года спустя
Статья Янна Лекуна и др. (1989) «Обратное распространение, применяемое к распознаванию рукописных почтовых индексов», я полагаю, имеет определенное историческое значение, поскольку, насколько мне известно, это самое раннее в реальном мире применение нейронной сети, обученной сквозному обратному распространению. За исключением крошечного набора данных (7291 изображение цифр в оттенках серого размером 16×16) и крошечной используемой нейронной сети (всего 1000 нейронов), эта статья выглядит удивительно современно сегодня, 33 года спустя — в ней представлен набор данных, описана архитектура нейронной сети, функция потерь, оптимизация и приведены показатели ошибок экспериментальной классификации по наборам обучения и тестов. Все это очень узнаваемо и печатается как современная статья по глубокому обучению, за исключением того, что она написана 33 года назад. Итак, я решил воспроизвести статью 1) для развлечения, но 2) чтобы использовать упражнение в качестве примера о природе прогресса в глубоком обучении.
Реализация. Я старался следовать статье как можно точнее и заново внедрил все в PyTorch в этом репо karpathy / lecun1989-repro на github. Оригинальная сеть была реализована на лиспе с использованием симулятора обратного распространения 1988 года Ботту и Лекуна SN (позже названного Lush). Статья на французском, поэтому я не могу ее прочитать, но из синтаксиса похоже, что вы можете указать нейронные сети, используя высокоуровневый API, аналогичный тому, что вы сделали бы в чем-то вроде PyTorch сегодня. В качестве краткого замечания по проектированию программного обеспечения современные библиотеки приняли дизайн, который разделяется на 3 компонента: 1) быстрая библиотека общих тензоров (C / CUDA), которая реализует базовые математические операции над многомерными тензорами, и 2) движок autograd, который отслеживает график прямых вычислений и может генерировать операции для обратного прохода, и 3) высокоуровневый API с поддержкой глубокого обучения (Python) для общих операций глубокого обучения, слоев, архитектур, оптимизаторов, функций потерь и т.д.
Обучение. В ходе обучения мы должны выполнить 23 прохода по обучающему набору из 7291 примера, в общей сложности 167 693 представления (пример, метка) нейронной сети. Исходная сеть обучалась в течение 3 дней на рабочей станции SUN-4/260. Я запустил свою реализацию на своем процессоре MacBook Air (M1), который обработал ее примерно за 90 секунд (~ 3000-кратное наивное ускорение). Моя conda настроена на использование собственных сборок arm64, а не эмуляции Rosetta. Ускорение могло бы быть более значительным, если бы PyTorch поддерживал все возможности M1 (включая GPU и NPU), но это, похоже, все еще находится в разработке. Я также наивно пытался запустить код на графическом процессоре A100, но обучение на самом деле было медленнее, скорее всего, потому, что сеть такая крошечная (4-уровневая convnet с 12 каналами, всего 9760 параметров, 64 Тыс. MAC-адресов, 1 тыс. активаций), а SGD использует только один пример за раз. Тем не менее, если бы кто-то действительно хотел решить эту проблему с помощью современного оборудования (A100) и программной инфраструктуры (CUDA, PyTorch), нам нужно было бы заменить SGD для каждого примера на полноценное пакетное обучение, чтобы максимально использовать графический процессор и, скорее всего, добиться еще примерно 100-кратного ускорения задержки обучения.
Воспроизведение производительности 1989 года. Оригинальная статья сообщает о следующих результатах:
eval: split train. loss 2.5e-3. error 0.14%. misses: 10
eval: split test . loss 1.8e-2. error 5.00%. misses: 102
Пока мой обучающий скрипт repro.py в его текущем виде печатается в конце 23-го прохода:
eval: split train. loss 4.073383e-03. error 0.62%. misses: 45
eval: split test . loss 2.838382e-02. error 4.09%. misses: 82
Итак, я воспроизвожу цифры приблизительно, но не точно. К сожалению, точное воспроизведение, скорее всего, невозможно, потому что исходный набор данных, я полагаю, был утерян со временем. Вместо этого мне пришлось смоделировать это, используя больший набор данных MNIST (ха, никогда не думал, что скажу это), взяв его 28×28 цифр, уменьшив их до 16×16 пикселей с помощью билинейной интерполяции и случайным образом без замены извлекая из него правильное количество примеров обучающего и тестового наборов. Но я уверен, что замешаны и другие преступники. Например, статья немного слишком абстрактна в описании схемы инициализации веса, и я подозреваю, что в файле pdf есть некоторые ошибки форматирования, которые, например, стирают точки ».», делая »2.5» похожим на »2,5», и потенциально (я думаю?) стирание квадратных корней. Например. нам говорят, что инициализация веса берется из равномерного »2 4 / F», где F — разветвление, но я предполагаю, что это наверняка (?) означает »2,4 / sqrt (F)», где sqrt помогает сохранить стандартное отклонение выходных данных. Специфическая структура разреженной связности между слоями сети H1 и H2 также не учитывается, в документе просто говорится, что она «выбрана в соответствии со схемой, которая здесь не будет обсуждаться», поэтому мне пришлось сделать несколько разумных предположений здесь с перекрывающейся блочной разреженной структурой. В статье также утверждается, что используется нелинейность tanh, но я беспокоюсь, что на самом деле это мог быть «нормализованный tanh», который отображает ntanh (1) = 1, и, возможно, с добавлением уменьшенного пропускного соединения, которое было модным в то время, чтобы гарантировать наличие хотя бы небольшого градиента в плоских хвостах tanh. Наконец, в статье используется «специальная версия алгоритма Ньютона, которая использует положительное диагональное приближение Гессиана», но я использовал SGD только потому, что он значительно проще и, согласно статье, «считается, что этот алгоритм не приведет к значительному увеличению скорости обучения».
Мошенничество с путешествиями во времени. Примерно с этого момента началась моя любимая часть. Мы живем здесь, через 33 года в будущем, и глубокое обучение является очень активной областью исследований. Насколько мы можем улучшить первоначальный результат, используя наше современное понимание и 33 года исследований и разработок? Мой первоначальный результат был:
eval: split train. loss 4.073383e-03. error 0.62%. misses: 45
eval: split test . loss 2.838382e-02. error 4.09%. misses: 82
Первое, о чем мне немного рассказали, это то, что мы делаем простую классификацию по 10 категориям, но в то время это моделировалось как регрессия среднеквадратичной ошибки (MSE) в целевые значения -1 (для отрицательного класса) или + 1 (для положительного класса), с выходными нейронами, которые также имели нелинейность tanh. Итак, я удалил tanh на выходных слоях, чтобы получить логики классов, и заменил стандартную (многоклассовую) функцию перекрестной потери энтропии. Это изменение значительно уменьшило ошибку при обучении, полностью переоснастив обучающий набор:
eval: split train. loss 9.536698e-06. error 0.00%. misses: 0
eval: split test . loss 9.536698e-06. error 4.38%. misses: 87
Я подозреваю, что нужно быть гораздо более осторожным с деталями инициализации веса, если ваш выходной слой имеет (насыщающую) нелинейность tanh и ошибку MSE поверх нее. Далее, по моему опыту, очень тонко настроенный SGD может работать очень хорошо, но современный Adam optimizer (скорость обучения 3–4, конечно :)) почти всегда является надежным базовым уровнем и практически не нуждается в настройке. Итак, чтобы повысить мою уверенность в том, что оптимизация не снижает производительность, я переключился на AdamW с LR 3e-4 и уменьшил его до 1e-4 в ходе обучения, дав:
eval: split train. loss 0.000000e+00. error 0.00%. misses: 0
eval: split test . loss 0.000000e+00. error 3.59%. misses: 72
Это дало немного улучшенный результат по сравнению с SGD, за исключением того, что мы также должны помнить, что из-за параметров по умолчанию произошло небольшое снижение веса, что помогает бороться с ситуацией переобучения. Поскольку мы все еще сильно переобучаемся, далее я представил простую стратегию увеличения данных, при которой я сдвигаю входные изображения максимум на 1 пиксель по горизонтали или вертикали. Однако, поскольку это имитирует увеличение размера набора данных, мне также пришлось увеличить количество проходов с 23 до 60 (я убедился, что простое наивное увеличение проходов в исходной настройке существенно не улучшило результаты):
eval: split train. loss 8.780676e-04. error 1.70%. misses: 123
eval: split test . loss 8.780676e-04. error 2.19%. misses: 43
Как видно из ошибки теста, это немного помогло! Увеличение данных — довольно простая и очень стандартная концепция, используемая для борьбы с переобучением, но я не видел, чтобы она упоминалась в статье 1989 года, возможно, это было более недавнее нововведение (?). Поскольку мы все еще немного переобучаемся, я обратился к другому современному инструменту в наборе инструментов, Dropout. Я добавил слабое выпадение в 0,25 непосредственно перед слоем с наибольшим количеством параметров (H3). Поскольку dropout устанавливает активации равными нулю, нет особого смысла использовать его с tanh, который имеет активный диапазон [-1,1], поэтому я также заменил все нелинейности на гораздо более простую функцию активации ReLU. Поскольку отсев создает еще больше шума во время тренировки, нам также приходится тренироваться дольше, выполняя до 80 проходов, но отдавая:
eval: split train. loss 2.601336e-03. error 1.47%. misses: 106
eval: split test . loss 2.601336e-03. error 1.59%. misses: 32
Что приводит нас всего к 32 / 2007 ошибкам в тестовом наборе! Я убедился, что простая замена tanh → relu в исходной сети не дала существенного выигрыша, поэтому большая часть улучшений здесь происходит за счет добавления dropout. В итоге, если бы я перенесся во времени в 1989 год, я смог бы сократить количество ошибок примерно на 60%, сократив их с ~ 80 до ~ 30, а общий уровень ошибок в тестовом наборе составил ~ 1,5%. Этот выигрыш не был получен полностью бесплатно, потому что мы также почти в 4 раза увеличили время обучения, что увеличило бы время обучения 1989 года с 3 дней почти до 12. Но на задержку вывода это не повлияло бы. Остальные ошибки здесь:
Идем дальше. Однако, после замены MSE → Softmax, SGD → AdamW, добавления расширения данных, отсева и замены tanh → relu я начал сокращаться на низко висящих плодах идей. Я попробовал еще несколько способов (например, нормализацию веса), но существенно лучших результатов не получил. Я также пытался миниатюризировать визуальный преобразователь (ViT)) в «micro-ViT», который примерно соответствует количеству параметров и провалов, но не смог сравниться по производительности с convnet. Конечно, за последние 33 года было сделано много других инноваций, но многие из них (например, остаточные соединения, нормализации слоев / пакетов) становятся актуальными только в гораздо более крупных моделях и в основном помогают стабилизировать крупномасштабную оптимизацию. Дальнейший выигрыш на данном этапе, вероятно, должен был бы быть достигнут за счет увеличения размера сети, но это увеличило бы задержку вывода во время тестирования.
Мошенничество с данными. Другим подходом к повышению производительности было бы масштабирование набора данных, хотя для этого потребовались бы долларовые затраты на маркировку. Наш исходный исходный уровень воспроизведения, опять же для справки, был:
eval: split train. loss 4.073383e-03. error 0.62%. misses: 45
eval: split test . loss 2.838382e-02. error 4.09%. misses: 82
Используя тот факт, что нам доступен весь MNIST, мы можем просто попробовать увеличить обучающий набор примерно в 7 РАЗ (от 7 291 до 50 000 примеров). Продолжение базового обучения в течение 100 проходов уже показывает некоторое улучшение только благодаря добавленным данным:
eval: split train. loss 1.305315e-02. error 2.03%. misses: 60
eval: split test . loss 1.943992e-02. error 2.74%. misses: 54
Но дальнейшее сочетание этого с инновациями современных знаний (описанными в предыдущем разделе) дает наилучшую производительность на сегодняшний день:
eval: split train. loss 3.238392e-04. error 1.07%. misses: 31
eval: split test . loss 3.238392e-04. error 1.25%. misses: 24
Подводя итог, простое масштабирование набора данных в 1989 году было бы эффективным способом повысить производительность системы без каких-либо затрат на задержку вывода.
Размышления. Давайте обобщим то, что мы узнали, будучи путешественниками во времени 2022 года, изучающими современную технологию глубокого обучения 1989 года:
Прежде всего, за 33 года мало что изменилось на макроуровне. Мы все еще создаем дифференцируемые архитектуры нейронных сетей, состоящие из слоев нейронов, и оптимизируем их от начала до конца с помощью обратного распространения и стохастического градиентного спуска. Все выглядит удивительно знакомо, за исключением того, что оно меньше.
Набор данных по сегодняшним стандартам является младенцем: обучающий набор составляет всего 7291 изображение в оттенках серого размером 16×16. Сегодняшние наборы данных vision обычно содержат несколько сотен миллионов цветных изображений с высоким разрешением из Интернета (например, у Google есть JFT-300M, OpenAI CLIP обучался на 400M), но вырастают до нескольких миллиардов. Это примерно в 1000 раз больше информации в пикселях на изображение (384×384*3/(16×16)) в 100 000 раз больше количества изображений (1e9 / 1e4), что примерно в 100 000 000 раз больше данных в пикселях на входе.
Нейронная сеть также является младенцем: эта сеть 1989 года имеет около 9760 параметров, 64 тыс. MAC-адресов и 1 тыс. активаций. Современные нейронные сети (vision) имеют размер в несколько миллиардов параметров (1 000 000 X) и O (~ 1e12) MAC (~ 10 000 000X). Модели естественного языка могут охватывать триллионы параметров.
Современный классификатор, на обучение которого на рабочей станции уходило 3 дня, теперь обучается за 90 секунд на моем безвентиляторном ноутбуке (3000-кратное ускорение naive), и, скорее всего, возможен дальнейший прирост в ~ 100 раз при переключении на полнокомпонентную оптимизацию и использовании графического процессора.
Фактически, я смог настроить модель, дополнение, функцию потерь и оптимизацию на основе современных инноваций в области исследований и разработок, чтобы сократить частоту ошибок на 60%, сохранив набор данных и задержку тестирования модели неизменными.
Скромные успехи были достигнуты только за счет расширения набора данных.
Дальнейший значительный выигрыш, вероятно, должен был бы принести более крупная модель, которая потребовала бы большего объема вычислений и дополнительных исследований и разработок, чтобы помочь стабилизировать обучение при возрастающих масштабах. В частности, если бы я перенесся в 1989 год, я бы в конечном счете оказался выше своих возможностей по дальнейшему совершенствованию системы без более мощного компьютера.
Предположим, что уроки этого упражнения остаются неизменными во времени. Что это говорит о глубоком обучении 2022 года? Что бы путешественник во времени из 2055 года подумал о производительности нынешних сетей?
Нейронные сети 2055 года в основном такие же, как нейронные сети 2022 года на макроуровне, за исключением большего размера.
Наши наборы данных и модели сегодня выглядят как шутка. Оба они где-то примерно в 10 000 000 раз больше.
Можно обучить самые современные модели 2022 года примерно за 1 минуту, наивно тренируясь на своем персональном компьютере в качестве развлекательного проекта выходного дня.
Сегодняшние модели сформулированы неоптимально, и, просто изменяя некоторые детали модели, функцию потерь, увеличение или оптимизатор, мы можем примерно вдвое уменьшить ошибку.
Наши наборы данных слишком малы, и скромный выигрыш был бы получен только от масштабирования набора данных.
Дальнейший прогресс фактически невозможен без расширения вычислительной инфраструктуры и инвестирования в некоторые исследования и разработки по эффективному обучению моделей такого масштаба.
Но самая важная тенденция, которую я хочу прокомментировать, заключается в том, что весь процесс обучения нейронной сети с нуля для решения некоторой целевой задачи (например, распознавания цифр) быстро устаревает из-за тонкой настройки, особенно с появлением базовых моделей, таких как GPT. Эти базовые модели обучаются лишь несколькими учреждениями, располагающими значительными вычислительными ресурсами, и большинство приложений достигается за счет легкой тонкой настройки части сети, быстрого проектирования или необязательного этапа перегонки данных или модели в меньшие сети вывода специального назначения. Я думаю, нам следует ожидать, что эта тенденция будет очень живой и даже усилится. В ее самой экстремальной экстраполяции вам вообще не захочется обучать какие-либо нейронные сети. В 2055 году вы попросите мегамозг нейронной сети размером 10 000 000 X выполнить какую-либо задачу, обратившись к нему с речью (или мыслью) на английском. И если вы попросите достаточно вежливо, он выполнит. Да, вы тоже могли бы обучить нейронную сеть …, но зачем вам это?