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

Но не все функции имеют очевидную производную. Некоторые из них очень сложны, что делает поиск производной вручную трудоемким.

Кроме того, если мы откроем наш диапазон возможных функций миру кода, то получим циклы, операторы if-else, сложные объекты, функции, вызывающие другие функции, и многое другое. Вычисление производной сложной кодовой функции с помощью ручки и бумаги может занять месяцы!

Отличные новости! Возможно получение деривативов автоматически! Нет ручки и бумаги! Вы можете написать функцию, произнести магическое заклинание и получить производную бесплатно!

В Swift магическое заклинание звучит так:

import _Differentiation

Посмотрим, как это работает!

Вот знакомое f(x) = x²:

func f(x: Double) -> Double{
    return x * x
}

Получим производную! Используйте gradient(at: of:):

var gradientAtThree = gradient(at: 3, of: f)

gradient(at: of:) означает на английском языке «получить производную в этой точке от этой функции»)

мы знаем, что производная x² равна 2x, поэтому 2(3) должно быть 6:

print(“gradientAtThree:”, gradientAtThree)
// gradientAtThree: 6.0

Успех!

Это как волшебство!

Давайте сделаем шаг градиентного спуска, просто для удовольствия: возьмем наш градиент, который указывает вверх, и шагнем в противоположном направлении (то есть отрицаем градиент). (Затем увеличьте этот шаг на learningRate из 0.1, чтобы он не был слишком большим):

let stepInXDirection = -gradientAtThree * 0.1
let newInput = 3 + stepInXDirection

после выполнения этого шага выход функции должен быть ближе к нулю:

print(“oldOutput:”, f(x: 3))
print(“newOutput:”, f(x: newInput))
//oldOutput: 9.0
//newOutput: 5.76

Действительно, он вырос с 9,0 до 5,76!

Хорошо, а что насчет чего-то более сложного?

func randomUselessFunction(a: Double, b: Double) -> Double{
    var result: Double = 0
    if a < 3{
        result += sin(a)
    } 
    else{
        result -= log(a)
    }
    for i in 0..<10{
        result += b * Double(i)
    }
    return result
}

Давайте попробуем случайную точку:

let a: Double = -17
let b: Double = 22
let gradientAtAandB = gradient(at: a, b, of: randomUselessFunction)
print(“function output:”, randomUselessFunction(a: a, b: b))
print(“derivatives to a and b at that point :”, gradientAtAandB)
//function output : 990.9613974918796
//derivatives to a and b at that point : (-0.2751633, 45.0)

Производная по «b» равна 45,0. Давайте посмотрим, действительно ли шаг «b» на 1 увеличивает вывод на 45:

let newB = b + 1
print(“original output:”, randomUselessFunction(a: a, b: b))
print(“output with step:”, randomUselessFunction(a: a, b: newB))
//original output: 990.9613974918796
//output with step: 1035.9613974918796

Ага!

Вы когда-нибудь думали, что сможете выполнить градиентный спуск для любого старого фрагмента кода?

Будущее - сегодня!

В Части 3 вы узнаете немного больше об AutoDiff API.

Автоматическая дифференциация в Swift все еще находится в стадии бета-тестирования. Вы можете скачать цепочку инструментов Xcode с включенным import _Differentiable здесь (Вы должны использовать цепочку инструментов под заголовком Снимки -> Разработка магистрали (основной)).

Когда компилятор начнет выдавать вам ошибки, которые вы не узнаете, ознакомьтесь с этим кратким руководством по менее зрелым аспектам дифференциального Swift. (Автоматическая дифференциация в Swift прошла долгий путь, но все еще есть острые углы. Однако они постепенно исчезают!)

Автоматическая дифференциация в Swift существует благодаря авторам Differentiable Swift (Ричард Вей, Дэн Чжэн, Марк Раси, Брэд Ларсон и др.) и Сообществу Swift!

См. последние запросы на включение AutoDiff здесь.