~~~~~~~~~~~~~~~~~~~~ Arc.Ask3.Ru ~~~~~~~~~~~~~~~~~~~~~ 
Номер скриншота №:
✰ 168559836BED2390CDC86B240199B938__1715258460 ✰
Заголовок документа оригинал.:
✰ Automatic differentiation - Wikipedia ✰
Заголовок документа перевод.:
✰ Автоматическое дифференцирование — Википедия ✰
Снимок документа находящегося по адресу (URL):
✰ https://en.wikipedia.org/wiki/Automatic_differentiation ✰
Адрес хранения снимка оригинал (URL):
✰ https://arc.ask3.ru/arc/aa/16/38/168559836bed2390cdc86b240199b938.html ✰
Адрес хранения снимка перевод (URL):
✰ https://arc.ask3.ru/arc/aa/16/38/168559836bed2390cdc86b240199b938__translat.html ✰
Дата и время сохранения документа:
✰ 09.06.2024 13:11:45 (GMT+3, MSK) ✰
Дата и время изменения документа (по данным источника):
✰ 9 May 2024, at 15:41 (UTC). ✰ 

~~~~~~~~~~~~~~~~~~~~~~ Ask3.Ru ~~~~~~~~~~~~~~~~~~~~~~ 
Сервисы Ask3.ru: 
 Архив документов (Снимки документов, в формате HTML, PDF, PNG - подписанные ЭЦП, доказывающие существование документа в момент подписи. Перевод сохраненных документов на русский язык.)https://arc.ask3.ruОтветы на вопросы (Сервис ответов на вопросы, в основном, научной направленности)https://ask3.ru/answer2questionТоварный сопоставитель (Сервис сравнения и выбора товаров) ✰✰
✰ https://ask3.ru/product2collationПартнерыhttps://comrades.ask3.ru


Совет. Чтобы искать на странице, нажмите Ctrl+F или ⌘-F (для MacOS) и введите запрос в поле поиска.
Arc.Ask3.ru: далее начало оригинального документа

Автоматическое дифференцирование — Википедия Jump to content

Автоматическая дифференциация

Из Википедии, бесплатной энциклопедии

В математике и компьютерной алгебре автоматическое дифференцирование ( auto-дифференциация , autodiff или AD ), также называемое алгоритмическим дифференцированием , вычислительным дифференцированием , [1] [2] представляет собой набор методов вычисления частной производной функции, заданной компьютерной программой.

Автоматическое дифференцирование использует тот факт, что каждое компьютерное вычисление, каким бы сложным оно ни было, выполняет последовательность элементарных арифметических операций (сложение, вычитание, умножение, деление и т. д.) и элементарных функций ( exp , log , sin , cos и т. д.). Повторно применяя правило цепочки к этим операциям, частные производные произвольного порядка могут быть вычислены автоматически с точностью до рабочей точности и с использованием не более небольшого постоянного коэффициента большего количества арифметических операций, чем в исходной программе.

Отличие от других методов дифференциации [ править ]

Рисунок 1. Как автоматическая дифференциация связана с символической дифференциацией

Автоматическое дифференцирование отличается от символического дифференцирования и числового дифференцирования . Символьное дифференцирование сталкивается с трудностями преобразования компьютерной программы в одно математическое выражение и может привести к неэффективному коду. Численное дифференцирование (метод конечных разностей) может вносить ошибки округления в процесс дискретизации и отмены. Оба этих классических метода имеют проблемы с вычислением высших производных, где увеличивается сложность и ошибки. Наконец, оба этих классических метода медленны при вычислении частных производных функции по множеству входных данных, что необходимо для градиента на основе алгоритмов оптимизации . Автоматическая дифференциация решает все эти проблемы.

Приложения [ править ]

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

и накопление Прямое обратное

Цепное правило частных производных сложных функций [ править ]

Основой автоматического дифференцирования является разложение дифференциалов, обеспечиваемое цепным правилом частных производных сложных функций . Для простой композиции

правило цепочки дает

Два типа автоматического дифференцирования [ править ]

Обычно представлены два различных режима автоматического дифференцирования.

  • прямое накопление (также называемое восходящим , прямым или касательным режимом )
  • обратное накопление (также называемое нисходящим , обратным режимом или присоединенным режимом )

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

  • Прямое накопление вычисляет рекурсивное соотношение: с , и,
  • Обратное накопление вычисляет рекурсивное соотношение: с .

Значение частной производной, называемой семенем , распространяется вперед или назад и первоначально или . Прямое накопление оценивает функцию и вычисляет производную по одной независимой переменной за один проход. Для каждой независимой переменной поэтому необходим отдельный проход, в котором производная по этой независимой переменной устанавливается равной единице ( ) и всех остальных к нулю ( ). Напротив, обратное накопление требует оценки частичных функций для частных производных. Поэтому обратное накопление сначала оценивает функцию и вычисляет производные по всем независимым переменным за дополнительный проход.

Какой из этих двух типов следует использовать, зависит от количества разверток. Вычислительная сложность одной проходки пропорциональна сложности исходного кода.

  • Прямое накопление более эффективно, чем обратное накопление для функций f : R. н Р м при n m только n , поскольку необходимо проходов по сравнению с m проходами для обратного накопления.
  • Обратное накопление более эффективно, чем прямое накопление для функций f : R. н Р м с n m, только m поскольку необходимо проходов по сравнению с n проходами для прямого накопления.

Обратное распространение ошибок в многослойных персептронах — метод, используемый в машинном обучении , — является частным случаем обратного накопления. [2]

Форвардное накопление было введено Р.Э. Венгертом в 1964 году. [3] По словам Андреаса Гриванка, обратное накопление предлагалось с конца 1960-х годов, но изобретатель неизвестен. [4] Сеппо Линнаинмаа опубликовал обратное накопление в 1976 году. [5]

Форвардное накопление [ править ]

Форвардное накопление

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

Это можно обобщить на несколько переменных как матричное произведение якобианов .

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

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

Используя правило цепочки, если имеет предшественников в вычислительном графе:

Рисунок 2. Пример прямого накопления с вычислительным графом.

В качестве примера рассмотрим функцию:

Для ясности отдельные подвыражения помечены переменными .

Выбор независимой переменной, по которой проводится дифференцирование, влияет на начальные значения 1 и 2 . Учитывая интерес к производной этой функции по x 1 , начальные значения должны быть установлены следующим образом:

Если заданы начальные значения, значения распространяются с использованием правила цепочки, как показано. На рис. 2 показано графическое изображение этого процесса в виде вычислительного графа.

Операции по вычислению стоимости Операции по вычислению производной
(семя)
(семя)

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

Реализация [ править ]

Псевдокод [ править ]

Прямое накопление вычисляет функцию и производную (но только для одной независимой переменной) за один проход. что выражение Z будет получено относительно переменной V. Связанный вызов метода ожидает , Метод возвращает пару оцениваемой функции и ее производной. Метод рекурсивно обходит дерево выражений до тех пор, пока не будет достигнута переменная. Если запрашивается производная по этой переменной, ее производная равна 1, в противном случае — 0. Затем вычисляются частичная функция и частная производная. [6]

кортеж  <  float  ,  float  >   AssessmentAndDerive  (  Expression   Z  ,   Variable   V  )   { 
    if   isVariable  (  Z  ) 
       if   (  Z   =   V  )   return   {  valueOf  (  Z  ),   1  }; 
        еще   возврат   {  valueOf  (  Z  ),   0  }; 
     иначе   , если   (  Z   =   A   +   B  ) 
       {  a  ,   a  '  }   =   AssessmentAndDerive  (  A  ,   V  ); 
        {  b  ,   b  '  }   =   AssessmentAndDerive  (  B  ,   V  ); 
        вернуть   {  а   +   б  ,   а  '   +   б  '  }; 
     иначе   , если   (  Z   =   A   -   B  ) 
       {  a  ,   a  '  }   =   AssessmentAndDerive  (  A  ,   V  ); 
        {  b  ,   b  '  }   =   AssessmentAndDerive  (  B  ,   V  ); 
        вернуть   {  а   -   б  ,   а  '   -   б  '  }; 
     иначе   , если   (  Z   =   A   *   B  ) 
       {  a  ,   a  '  }   =   AssessmentAndDerive  (  A  ,   V  ); 
        {  b  ,   b  '  }   =   AssessmentAndDerive  (  B  ,   V  ); 
        return   {  a   *   b  ,   b   *   a  '   +   a   *   b  '  }; 
  } 
Питон [ править ]
класс   ValueAndPartial  : 
     def   __init__  (  self  ,   value  ,   parts  ): 
         self  .   значение   =   значение 
         себя  .   частичное   =   частичное 

     определение   toList  (  self  ): 
         return   [  self  .   ценность  ,   сам  .   частичный  ] 

 класс   Выражение  : 
     def   __add__  (  self  ,   other  ): 
         return   Plus  (  self  ,   other  ) 

     def   __mul__  (  self  ,   other  ): 
         return   Multiply  (  self  ,   other  ) 

 class   Variable  (  Expression  ): 
     def   __init__  (  self  ,   value  ): 
         self  .   значение   =   значение 

     def   AssessmentAndDerive  (  self  ,   переменная  ): 
         частичный   =   1   , если   сам   ==   переменная   , иначе   0 
         return   ValueAndPartial  (  self  .  value  ,   частичное  ) 

 класс   Plus  (  Выражение  ): 
     def   __init__  (  self  ,   выражениеA  ,   выражениеB  ): 
         self  .   выражениеA   =   выражениеA 
         self  .   выражениеB   =   выражениеB 

     def   AssessmentAndDerive  (  self  ,   переменная  ): 
         valueA  ,   partsA   =   self  .   выражение  А.   AssessmentAndDerive  (  переменная  )  .   toList  () 
         valueB  ,   частичныйB   =   self  .   выражениеБ  .   AssessmentAndDerive  (  переменная  )  .   toList  () 
         return   ValueAndPartial  (  valueA   +   valueB  ,   partsA   +   partsB  ) 

 класс   Multiply  (  Expression  ): 
     def   __init__  (  self  ,   выражениеA  ,   выражениеB  ): 
         self  .   выражениеA   =   выражениеA 
         self  .   выражениеB   =   выражениеB 

     def   AssessmentAndDerive  (  self  ,   переменная  ): 
         valueA  ,   partsA   =   self  .   выражениеА  .   AssessmentAndDerive  (  переменная  )  .   toList  () 
         значениеB ,   частичныйB   =   сам  .   выражениеБ  .   AssessmentAndDerive  (  переменная  )  .   toList  () 
         return   ValueAndPartial  (  valueA   *   valueB  ,   valueB   *   partsA   +   valueA   *   partsB  ) 

 # Пример: поиск частичных значений z = x * (x + y) + y * y at (x, y) = (2, 3) 
 x   =   Переменная  (  2  ) 
 y   =   Переменная  (  3  ) 
 z   знак равно   Икс   *   (  x   +   y  )   +   y   *   y 
 xPartial   =   z  .   оценитьAndDerive  (  x  )  .   частичный 
 yPartial   =   z  .   оценитьAndDerive  (  y  )  .   частичная 
 печать  (  "∂z/∂x ="  ,   xPartial  )    # Вывод: ∂z/∂x = 7 
 print  (  "∂z/∂y ="  ,   yPartial  )    # Вывод: ∂z/∂y = 8 
С++ [ править ]
#include   <iostream> 
 struct   ValueAndPartial   {   с плавающей запятой   значение  ,   частичное  ;    }; 
  структурная   переменная  ; 
  struct   Expression   { 
    virtual   ValueAndPartial   AssessmentAndDerive  (  Переменная   *  переменная  )   =   0  ; 
  }; 
  struct   Variable  :   public   Expression   { 
    с плавающей запятой   значение  ; 
     Переменная  (  с плавающей запятой   значение  )  :   значение  (  значение  )   {} 
    ValueAndPartial   AssessmentAndDerive  (  Переменная   *  переменная  )   { 
       float   частичное   =   (  это   ==   переменная  )   ?    1,0ф   :   0,0ф  ; 
        возврат   {  значение  ,   частичное  }; 
     } 
 }; 
  struct   Plus  :   public   Expression   { 
    Expression   *  a  ,   *  b  ; 
     Плюс  (  Выражение   *  a  ,   Выражение   *  b  )  :   a  (  a  ),   b  (  b  )   {} 
    ValueAndPartial   AssessmentAndDerive  (  Переменная   *  переменная  )   { 
       auto   [  valueA  ,   PartialA  ]   =   a  ->  AssessmentAndDerive  (  переменная  ); 
        auto   [  valueB  ,   частичныйB  ]   =   b  ->  AssessmentAndDerive  (  переменная  ); 
        возврат   {  значениеA   +   значениеB  ,   частичныйA   +   частичныйB  }; 
     } 
 }; 
  struct   Multiply  :   public   Expression   { 
    Expression   *  a  ,   *  b  ; 
     Умножение  (  Выражение   *  a  ,   Выражение   *  b  )  :   a  (  a  ),   b  (  b  )   {} 
    ValueAndPartial   AssessmentAndDerive  (  Переменная   *  переменная  )   { 
       auto   [  valueA  ,   PartialA  ]   =   a  ->  AssessmentAndDerive  (  переменная  ); 
        auto   [  valueB  ,   частичныйB  ]   =   b  ->  AssessmentAndDerive  (  переменная  ); 
        return   {  значениеA   *   значениеB  ,   значениеB   *   частичное A   +   значениеA   *   частичноеB  }; 
     } 
 }; 
  int   main   ()   { 
    // Пример: нахождение частей z = x * (x + y) + y * y в точке (x, y) = (2, 3) 
   Переменная   x  (  2  ),   y  (  3  ); 
     Плюс   p1  (  &  x  ,   &  y  );    Умножьте   m1  (  &  x  ,   &  p1  );    Умножьте   m2  (  &  y  ,   &  y  );    Плюс   z  (  &  m1  ,   &  m2  ); 
     плавающий   xPartial   =   z  .   оценитьAndDerive  (  &  x  ).   частичный  ; 
     плавать   yPartial   =   z  .   оценитьAndDerive  (  &  y  ).   частичный  ; 
     std  ::  cout   <<   "∂z/∂x = "   <<   xPartial   <<   ", " 
              <<   "∂z/∂y = "   <<   yPartial   <<   std  ::  endl  ; 
     // Вывод: ∂z/∂x = 7, ∂z/∂y = 8 
    return   0  ; 
  } 

Обратное накопление [ править ]

Обратное накопление

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

При обратном накоплении процентная величина представляет собой сопряженную величину , обозначенную чертой. ; это производная выбранной зависимой переменной по отношению к подвыражению :

Используя правило цепочки, если имеет преемников в вычислительном графе:

Обратное накопление проходит по цепному правилу снаружи внутрь или, в случае вычислительного графа на рисунке 3, сверху вниз. Пример функции имеет скалярное значение, поэтому для вычисления производной используется только одно начальное значение, и для вычисления (двухкомпонентного) градиента требуется только один проход вычислительного графа. Это только половина работы по сравнению с прямым накоплением, но обратное накопление требует хранения промежуточных переменных w i, а также инструкций, которые их создали, в структуре данных, известной как «лента» или список Венгерта. [7] (однако Венгерт опубликовал прямое накопление, а не обратное накопление). [3] ), что может занимать значительный объем памяти, если вычислительный граф большой. Это можно в некоторой степени смягчить, сохраняя только подмножество промежуточных переменных, а затем реконструируя необходимые рабочие переменные путем повторения оценок - метод, известный как рематериализация . Контрольные точки также используются для сохранения промежуточных состояний.

Рисунок 3: Пример обратного накопления с вычислительным графиком

Операции по вычислению производной с использованием обратного накопления показаны в таблице ниже (обратите внимание на обратный порядок):

Операции по вычислению производной

Графиком потока данных вычисления можно манипулировать для расчета градиента исходного расчета. Это делается путем добавления присоединенного узла для каждого основного узла, соединенного присоединенными ребрами, которые параллельны основным ребрам, но текут в противоположном направлении. Узлы сопряженного графа представляют собой умножение на производные функций, рассчитанных узлами простого графа. Например, сложение в первичном элементе приводит к разветвлению в присоединенном; разветвление в первичном вызывает сложение в сопряженном; [а] унарная ) функция y = f ( x ) в первопричинах = ş f ′( x ; в сопряженных и т. д.

Реализация [ править ]

Псевдокод [ править ]

Обратное накопление требует двух проходов: при прямом проходе сначала вычисляется функция, а частичные результаты кэшируются. При обратном проходе вычисляются частные производные, а полученное ранее значение подвергается обратному распространению. Соответствующий вызов метода ожидает, что выражение Z будет производным и начальным значением будет производное значение родительского выражения. Для верхнего выражения Z, полученного относительно Z, это значение равно 1. Метод рекурсивно обходит дерево выражений до тех пор, пока не будет достигнута переменная, и добавляет текущее начальное значение к производному выражению. [8] [9]

void   Derivative  (  Выражение   Z  ,   с плавающей запятой   семя  )   { 
    if   isVariable  (  Z  ) 
       partsialDerivativeOf  (  Z  )   +=   семя  ; 
     иначе   , если   (  Z   =   A   +   B  ) 
       получить  (  A  ,   семя  ); 
        получить  (  B  ,   семя  ); 
     иначе   , если   (  Z   =   A   -   B  ) 
       получить  (  A  ,   семя  ); 
        получить  (  B  ,   -  семя  ); 
     иначе,   если   (  Z   =   A   *   B  ) 
       получить  (  A  ,   valueOf  (  B  )   *   семя  ); 
        получить  (  B  ,   valueOf  (  A  )   *   семя  ); 
  } 
Питон [ править ]
 класса  Выражение  : 
     def   __add__  (  self  ,   Other  ): 
         return   Plus  (  self  ,   Other  ) 
     def   __mul__  (  self  ,   Other  ): 
         return   Multiply  (  self  ,   Other  ) 

 class   Variable  (  Expression  ): 
     def   __init__  (  self  ,   value  ): 
         self  .   значение   =   значение 
         себя  .   частичный   =   0 

     определение   оценки  (  self  ): 
         пройти 

     определение   определения  (  я  ,   семя  ): 
         self  .   частичный   +=   начальный 

 класс   Плюс  (  Выражение  ): 
     def   __init__  (  self  ,   выражениеA  ,   выражениеB  ): 
         self  .   выражениеA   =   выражениеA 
         self  .   выражениеB   =   выражениеB 
         self  .   значение   =   Нет 

     Def   Assessment  (  self  ): 
         self  .   выражениеА  .   оценить  () 
         себя  .   выражениеБ  .   оценить  () 
         себя  .   значение   =   я  .   выражениеА  .   ценность   +   сам  .   выражениеБ  .   значение 

     def   получить  (  self  ,   семя  ): 
         self  .   выражениеА  .   получить  (  семя  ) 
         себя  .   выражениеБ  .  получения  (  семя  ) 

  класс   Multiply  (  Выражение  ): 
     def   __init__  (  self  ,   выражениеA  ,   выражениеB  ): 
         self  .   выражениеA   =   выражениеA 
         self  .   выражениеB   =   выражениеB 
         self  .   значение   =   Нет 

     Def   Assessment  (  self  ): 
         self  .   выражениеА  .   оценить  () 
         себя  .   выражениеБ  .   оценить  () 
         себя  .   значение   =   я  .   выражениеА  .   значение   *   сам  .   выражениеБ  .   значение 

     def   получить  (  self  ,   семя  ): 
         self  .   выражениеА  .   получить  (  self.выражениеB.значение  *    семя  ) 
         self  .   выражениеБ  .   Dereve  (  self  .  ExpressionA  .  Value   *   Seed  ) 

 # Пример: нахождение частичных значений z = x * (x + y) + y * y at (x, y) = (2, 3) 
Икс   =   Переменная  (  2  ) 
 y   =   Переменная  (  3  ) 
 z   знак равно   Икс   *   (  Икс   +   y  )   +   y   *   y 
 z  .   Assessment  ) 
 print  (  "z="  ,   z.value  )  19  .          # Вывод: z = 
 z  (   Deriv  (  1  ) 
 print  (  "∂z/∂x ="  ,   x  .  частичное  )    # Выход: ∂z/∂x = 7 
 print  (  "∂z/∂y ="  ,   y  .  частичное  )    # Выход: ∂z/ ∂у = 8 
С++ [ править ]
#include   <iostream> 
 struct   Expression   { 
    с плавающей запятой   значение  ; 
     виртуальная   недействительная   оценка  ()   =   0  ; 
     виртуальная   пустота   получения  (  floatseed   )   =   0  ; 
  }; 
  struct   Variable  :   public   Expression   { 
    float   частичное  ; 
     Переменная  (  float   _value  )   { 
       value   =   _value  ; 
        частичный   =   0  ; 
     } 
    void   Assessment  ()   {} 
    void   Derivate  (  с плавающей запятой   seed  )   { 
       частичное   +=   семя  ; 
     } 
 }; 
  struct   Plus  :   public   Expression   { 
    Expression   *  a  ,   *  b  ; 
     Плюс  (  Выражение   *  a  ,   Выражение   *  b  )  :   a  (  a  ),   b  (  b  )   {} 
    void   Assessment  ()   { 
       a  ->  Assessment  (); 
        б  ->  оценить  (); 
        значение   =   a  ->  значение   +   b  ->  значение  ; 
     } 
    void   Derivate  (  floatseed   seed  )   { 
       a  ->  Derivate  (  )  ; 
        б  ->  получить  (  семя  ); 
     } 
 }; 
  struct   Multiply  :   public   Expression   { 
    Expression   *  a  ,   *  b  ; 
     Умножить  (  Выражение   *  a  ,   Выражение   *  b  )  :   a  (  a  ),   b  (  b  )   {} 
    void   Assessment  ()   { 
       a  ->  Assessment  (); 
        б  ->  оценить  (); 
        значение   =   a  ->  значение   *   b  ->  значение  ; 
     } 
    void   Derivate  (  floatseed   )  ;   { 
       a  ->  Deer  (  b  ->  значение   *   seed  ) 
        b  ->  получить  (  a  ->  значение   *   семя  ); 
     } 
 }; 
  int   main   ()   { 
    // Пример: нахождение частей z = x * (x + y) + y * y at (x, y) = (2, 3) 
    Переменная   x  (  2  ),   y  (  3  ); 
     Плюс   p1  (  &  x  ,   &  y  );    Умножьте   m1  (  &  x  ,   &  p1  );    Умножьте   m2  (  &  y  ,   &  y  );    Плюс   z  (  &  m1  ,   &  m2  ); 
     з  .   оценивать  (); 
     std  ::  cout   <<   "z = "   <<   z  .   значение   <<   std  ::  endl  ; 
     // Вывод: z = 19 
    z  .   вывести  (  1  ); 
     std  ::  cout   <<   "∂z/∂x = "   <<   x  .   частичный   <<   ", " 
              <<   "∂z/∂y = "  <<   й  .   частичный   <<   std  ::  endl  ; 
     // Вывод: ∂z/∂x = 7, ∂z/∂y = 8 
    return   0  ; 
  } 

прямого и накопления Помимо обратного

Прямое и обратное накопление — это всего лишь два (крайних) способа обойти правило цепочки. Проблема вычисления полного якобиана f : R н Р м с минимальным количеством арифметических операций, известна как задача оптимального накопления якобиана (OJA), которая является NP-полной . [10] Центральным элементом этого доказательства является идея о том, что между локальными партиалами, обозначающими ребра графа, могут существовать алгебраические зависимости. В частности, две или более метки ребер могут быть признаны равными. Сложность проблемы остается открытой, если предположить, что все метки ребер уникальны и алгебраически независимы.

Автоматическое дифференцирование с чисел использованием двойных

Автоматическое дифференцирование в прямом режиме осуществляется путем расширения алгебры действительных чисел и получения новой арифметики . К каждому числу добавляется дополнительный компонент, представляющий производную функции от этого числа, а все арифметические операторы расширяются для расширенной алгебры. Расширенная алгебра — это алгебра двойственных чисел .

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

с использованием .

Теперь полиномы можно вычислять с помощью этой расширенной арифметики. Если , затем

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

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

и вообще для примитивной функции ,
где и являются производными от относительно его первого и второго аргументов соответственно.

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

Реализация [ править ]

Ниже приведен пример реализации, основанной на подходе двойного числа.

Псевдокод [ править ]

Двойной плюс (Двойной А, Двойной Б) {
   возвращаться {
     реальнаяЧасть(A) + реальнаяЧасть(B),
     бесконечно малаяЧасть(A) + бесконечно малаяЧасть(B)
   };
 }
 Двойной минус (Двойной А, Двойной Б) {
   возвращаться {
     реальнаяЧасть(А) - реальнаяЧасть(В),
     бесконечно малая часть (A) - бесконечно малая часть (B)
   };
 }
 Двойное умножение (Двойной A, Двойной B) {
   возвращаться {
     реальнаяЧасть(A) * реальнаяЧасть(B),
     реальнаяЧасть(B) * бесконечно малаяЧасть(A) + реальнаяЧасть(A) * бесконечно малаяЧасть(B)
   };
 }
 Х = {х, 0};
 Y = {у, 0};
 Эпсилон = {0, 1};
 xPartial = бесконечно малаяЧастьOf(f(X + Эпсилон, Y));
 yPartial = бесконечно малаяЧастьOf(f(X, Y + Эпсилон)); 

Питон [ править ]

class   Dual  : 
     def   __init__  (  self  ,   RealPart  ,   InfinitsimalPart  =  0  ): 
         self  .   RealPart   =   RealPart 
         self  .   InfinitesimalPart   =   InfinitesimalPart 

     def   __add__  (  self  ,   other  ) 
         return   Dual  ( 
             self.realPart  +  other.realPart   other   Other  )  ,  * 
             self.infinitesimalPart  :  other.infinitesimalPart   +   other.realPart  def  : 
         ) 

     )   __mul__  (  self  ,  
         return   Dual  ( 
             self.realPart  ,   
             .  .realPart   *   self  .  InfinitesimalPart   +   self  .  RealPart   *   Other  # Пример :  InfinitesimalPart 
         ) 

 нахождение частичных значений z = x * (x + y) + y * y at (x, y) = (2, 3) 
 def   f  (  x  ,   y  ): 
     return   x   *   (  x   +   y  )   +   y   *   y 
 x   =   Dual  (  2  ) 
 y   =   Dual  (  3  ) 
 эпсилон   =   Dual  (  0  ,   1  ) 
 a   =   f  (  x   +   epsilon  ,   y  ) 
 b   =   f  (  x  ,   y   +   epsilon  ) 
 print  (  "∂z/∂x ="  ,   a  .  InfinitesimalPart  )    # Вывод: ∂z/∂x = 7 
 print  (  "∂z/∂y ="  ,   b  .  InfinitesimalPart  )    # Вывод: ∂z/∂y = 8 

С++ [ править ]

#include   <iostream> 
 struct   Dual   { 
    float   RealPart  ,   InfinitesimalPart  ; 
     Dual  (  float   realPart  ,   float   InfinitesimalPart  =  0  )  :   RealPart  (  realPart  ),   InfinitesimalPart  (  InfinitesimalPart  )   {} 
    Двойной   оператор  +  (  Dual   Other  )   { 
       return   Dual  ( 
          realPart   +   Other  .  RealPart  , 
          InfinitesimalPart   +   Other  .  InfinitsimalPart 
       ); 
     } 
    Двойной   оператор  *  (  Dual   Other  )   { 
       return   Dual  ( 
          realPart   *   Other  .  RealPart  , 
          Other  .  RealPart   *   InfinitesimalPart   +   RealPart   *   Other  .  InfinitesimalPart 
       ); 
     } 
 }; 
  // Пример: нахождение частей z = x * (x + y) + y * y в (x, y) = (2, 3) 
 Dual   f  (  Dual   x  ,   Dual   y  )   {   return   x   *   (  x   +   y  )   +   y   *   y  ;    } 
 int   main   ()   { 
    Dual   x   =   Dual  (  2  ); 
     Двойной   у   =   Двойной  (  3  ); 
     Двойной   эпсилон   =   Двойной  (  0  ,   1  ); 
     Двойной   a   =   f  (  x   +   эпсилон  ,   y  ); 
     Двойной   b   =   f  (  x  ,   y   +   эпсилон  ); 
     std  ::  cout   <<   "∂z/∂x = "   <<   a  .   бесконечно малая часть   <<   ", " 
              <<   "∂z/∂y = "   <<   b  .   бесконечно малая часть   <<   std  ::  endl  ; 
     // Вывод: ∂z/∂x = 7, ∂z/∂y = 8 
    return   0  ; 
  } 

Векторные аргументы и функции [ править ]

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

Высокий порядок и множество переменных [ править ]

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

Реализация [ править ]

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

Трансформация исходного кода (SCT) [ править ]

Рисунок 4. Пример того, как может работать преобразование исходного кода.

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

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

Перегрузка операторов (ОО) [ править ]

Рисунок 5. Пример того, как может работать перегрузка операторов.

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

Перегрузка операторов и преобразование исходного кода [ править ]

Перегруженные операторы можно использовать для извлечения графика оценки с последующим автоматическим созданием AD-версии основной функции во время выполнения. В отличие от классического ОО ААД, такая AD-функция не меняется от одной итерации к следующей. Следовательно, для каждой выборки Xi возникают какие-либо накладные расходы во время выполнения объектно-ориентированной интерпретации или интерпретации ленты.

Поскольку AD-функция генерируется во время выполнения, ее можно оптимизировать, чтобы учесть текущее состояние программы и предварительно вычислить определенные значения. Кроме того, его можно сгенерировать таким образом, чтобы последовательно использовать встроенную векторизацию ЦП для обработки 4(8)-двойных фрагментов пользовательских данных (ускорение AVX2\AVX512 x4-x8). С учетом многопоточности такой подход может привести к окончательному ускорению порядка 8 × #Cores по сравнению с традиционными инструментами AAD. Эталонная реализация доступна на GitHub. [11]

См. также [ править ]

Примечания [ править ]

  1. ^ С точки зрения весовых матриц сопряженным является транспонирование . Сложение – это ковектор , с и разветвление - это вектор с

Ссылки [ править ]

  1. ^ Найдингер, Ричард Д. (2010). «Введение в автоматическое дифференцирование и объектно-ориентированное программирование MATLAB» (PDF) . Обзор СИАМ . 52 (3): 545–563. CiteSeerX   10.1.1.362.6580 . дои : 10.1137/080743627 . S2CID   17134969 .
  2. ^ Перейти обратно: а б Байдин, Атилим Гюнес; Перлмуттер, Барак; Радуль Алексей Андреевич; Сискинд, Джеффри (2018). «Автоматическая дифференциация в машинном обучении: опрос» . Журнал исследований машинного обучения . 18 : 1–43.
  3. ^ Перейти обратно: а б Р.Э. Венгерт (1964). «Простая автоматическая программа оценки производных» . Комм. АКМ . 7 (8): 463–464. дои : 10.1145/355586.364791 . S2CID   24039274 .
  4. ^ Гриванк, Андреас (2012). «Кто изобрел обратный способ дифференциации?» (PDF) . Истории оптимизации, Documenta Matematica . Дополнительный том ISMP: 389–400. дои : 10.4171/dms/6/38 . ISBN  978-3-936609-58-5 .
  5. ^ Линнаинмаа, Сеппо (1976). «Разложение Тейлора накопленной ошибки округления». БИТ Численная математика . 16 (2): 146–160. дои : 10.1007/BF01931367 . S2CID   122357351 .
  6. ^ Максимилиан Э. Шюле, Максимилиан Шпрингер, Альфонс Кемпер , Томас Нойманн (2022). «Оптимизация кода LLVM для автоматического дифференцирования». Материалы шестого семинара по управлению данными для сквозного машинного обучения . стр. 1–4. дои : 10.1145/3533028.3533302 . ISBN  9781450393751 . S2CID   248853034 . {{cite book}}: CS1 maint: несколько имен: список авторов ( ссылка )
  7. ^ Бартоломью-Биггс, Майкл; Браун, Стивен; Кристиансон, Брюс; Диксон, Лоуренс (2000). «Автоматическое дифференцирование алгоритмов». Журнал вычислительной и прикладной математики . 124 (1–2): 171–190. Бибкод : 2000JCoAM.124..171B . дои : 10.1016/S0377-0427(00)00422-2 . hdl : 2299/3010 .
  8. ^ Максимилиан Э. Шюле, Харальд Ланг, Максимилиан Шпрингер, Альфонс Кемпер , Томас Нойманн , Стефан Гюннеманн (2021). «Машинное обучение в базе данных с использованием SQL на графических процессорах». 33-я Международная конференция по управлению научными и статистическими базами данных . стр. 25–36. дои : 10.1145/3468791.3468840 . ISBN  9781450384131 . S2CID   235386969 . {{cite book}}: CS1 maint: несколько имен: список авторов ( ссылка )
  9. ^ Максимилиан Э. Шюле, Харальд Ланг, Максимилиан Шпрингер, Альфонс Кемпер , Томас Нойманн , Стефан Гюннеманн (2022). «Рекурсивный SQL и поддержка графических процессоров для машинного обучения в базе данных» . Распределенные и параллельные базы данных . 40 (2–3): 205–259. дои : 10.1007/s10619-022-07417-7 . S2CID   250412395 . {{cite journal}}: CS1 maint: несколько имен: список авторов ( ссылка )
  10. ^ Науманн, Уве (апрель 2008 г.). «Оптимальное накопление якобиана NP-полное». Математическое программирование . 112 (2): 427–441. CiteSeerX   10.1.1.320.5665 . дои : 10.1007/s10107-006-0042-z . S2CID   30219572 .
  11. ^ «Библиотека прототипов AADC» . 22 июня 2022 г. — через GitHub.

Дальнейшее чтение [ править ]

Внешние ссылки [ править ]

Arc.Ask3.Ru: конец оригинального документа.
Arc.Ask3.Ru
Номер скриншота №: 168559836BED2390CDC86B240199B938__1715258460
URL1:https://en.wikipedia.org/wiki/Automatic_differentiation
Заголовок, (Title) документа по адресу, URL1:
Automatic differentiation - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть, любые претензии не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, денежную единицу можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)