Arestas afiadas em Swift Diferenciável

O Swift diferenciável já percorreu um longo caminho em termos de usabilidade. Aqui está um alerta sobre as partes que ainda não são óbvias. Conforme o progresso continua, este guia se tornará cada vez menor e você poderá escrever código diferenciável sem a necessidade de sintaxe especial.

rotações

Os loops são diferenciáveis, há apenas um detalhe a saber. Ao escrever o loop, envolva a parte onde você especifica sobre o que está fazendo o loop sem withoutDerivative(at:)

var a: [Float] = [1,2,3]

por exemplo:

for _ in a.indices 
{}

torna-se

for _ in withoutDerivative(at: a.indices) 
{}

ou:

for _ in 0..<a.count 
{}

torna-se

for _ in 0..<withoutDerivative(at: a.count) 
{}

Isso é necessário porque o membro Array.count não contribui para a derivada em relação à matriz. Apenas os elementos reais na matriz contribuem para a derivada.

Se você tem um loop em que usa manualmente um número inteiro como o limite superior, não há necessidade de usar withoutDerivative(at:) :

let iterations: Int = 10
for _ in 0..<iterations {} //this is fine as-is.

Mapear e reduzir

map e reduce têm versões especiais diferenciáveis ​​que funcionam exatamente como o que você está acostumado:

a = [1,2,3]
let aPlusOne = a.differentiableMap {$0 + 1}
let aSum = a.differentiableReduce(0, +)
print("aPlusOne", aPlusOne)
print("aSum", aSum)
aPlusOne [2.0, 3.0, 4.0]
aSum 6.0

Conjuntos de subscritos de matriz

Conjuntos de subscritos de array[0] = 0 ( array[0] = 0 ) não são diferenciáveis ​​imediatamente, mas você pode colar esta extensão:

extension Array where Element: Differentiable {
    @differentiable(where Element: Differentiable)
    mutating func updated(at index: Int, with newValue: Element) {
        self[index] = newValue
    }

    @derivative(of: updated)
    mutating func vjpUpdated(at index: Int, with newValue: Element)
      -> (value: Void, pullback: (inout TangentVector) -> (Element.TangentVector))
    {
        self.updated(at: index, with: newValue)
        return ((), { v in
            let dElement = v[index]
            v.base[index] = .zero
            return dElement
        })
    }
}

e a sintaxe da solução alternativa é a seguinte:

var b: [Float] = [1,2,3]

em vez disso:

b[0] = 17

escreva isso:

b.updated(at: 0, with: 17)

Vamos nos certificar de que funcione:

func plusOne(array: [Float]) -> Float{
  var array = array
  array.updated(at: 0, with: array[0] + 1)
  return array[0]
}

let plusOneValAndGrad = valueWithGradient(at: [2], in: plusOne)
print(plusOneValAndGrad)
(value: 3.0, gradient: [1.0])

O erro que você obterá sem esta solução alternativa é que a Differentiation of coroutine calls is not yet supported . Aqui está o link para ver o progresso em tornar esta solução alternativa desnecessária: https://bugs.swift.org/browse/TF-1277 (fala sobre Array.subscript._modify, que é o que é chamado nos bastidores quando você faz uma matriz conjunto de subscritos).

Float <-> Conversões Double

Se você estiver alternando entre Float e Double , seus construtores já não são diferenciáveis. Aqui está uma função que permitirá que você vá de Float a Double diferente.

(Alterne Float e Double no código abaixo, e você tem uma função que converte de Double para Float .)

Você pode fazer conversores semelhantes para qualquer outro tipo numérico real.

@differentiable
func convertToDouble(_ a: Float) -> Double {
    return Double(a)
}

@derivative(of: convertToDouble)
func convertToDoubleVJP(_ a: Float) -> (value: Double, pullback: (Double) -> Float) {
    func pullback(_ v: Double) -> Float{
        return Float(v)
    }
    return (value: Double(a), pullback: pullback)
}

Aqui está um exemplo de uso:

@differentiable
func timesTwo(a: Float) -> Double {
  return convertToDouble(a * 2)
}
let input: Float = 3
let valAndGrad = valueWithGradient(at: input, in: timesTwo)
print("grad", valAndGrad.gradient)
print("type of input:", type(of: input))
print("type of output:", type(of: valAndGrad.value))
print("type of gradient:", type(of: valAndGrad.gradient))
grad 2.0
type of input: Float
type of output: Double
type of gradient: Float

Transcendental e outras funções (sin, cos, abs, max)

Muitos transcendentais e outras funções integradas comuns já foram diferenciadas para Float e Double . Existem menos para Double que Float . Alguns também não estão disponíveis. Portanto, aqui estão algumas definições de derivadas manuais para lhe dar uma ideia de como fazer o que você precisa, caso ainda não tenha sido fornecido:

pow (veja o link para uma explicação derivada)

import Foundation

@usableFromInline
@derivative(of: pow) 
func powVJP(_ base: Double, _ exponent: Double) -> (value: Double, pullback: (Double) -> (Double, Double)) {
    let output: Double = pow(base, exponent)
    func pullback(_ vector: Double) -> (Double, Double) {
        let baseDerivative = vector * (exponent * pow(base, exponent - 1))
        let exponentDerivative = vector * output * log(base)
        return (baseDerivative, exponentDerivative)
    }

    return (value: output, pullback: pullback)
}

max

@usableFromInline
@derivative(of: max)
func maxVJP<T: Comparable & Differentiable>(_ x: T, _ y: T) -> (value: T, pullback: (T.TangentVector)
  -> (T.TangentVector, T.TangentVector))
{
    func pullback(_ v: T.TangentVector) -> (T.TangentVector, T.TangentVector) {
        if x < y {
            return (.zero, v)
        } else {
            return (v, .zero)
        }
    }
    return (value: max(x, y), pullback: pullback)
}

abdômen

@usableFromInline
@derivative(of: abs)
func absVJP<T: Comparable & SignedNumeric & Differentiable>(_ x: T)
  -> (value: T, pullback: (T.TangentVector) -> T.TangentVector)
{
    func pullback(_ v: T.TangentVector) -> T.TangentVector{
        if x < 0 {
            return .zero - v
        }
        else {
            return v
        }
    }
    return (value: abs(x), pullback: pullback)
}

sqrt (consulte o link para uma explicação derivada)

@usableFromInline
@derivative(of: sqrt) 
func sqrtVJP(_ x: Double) -> (value: Double, pullback: (Double) -> Double) {
    let output = sqrt(x)
    func pullback(_ v: Double) -> Double {
        return v / (2 * output)
    }
    return (value: output, pullback: pullback)
}

Vamos verificar se isso funciona:

let powGrad = gradient(at: 2, 2, in: pow)
print("pow gradient: ", powGrad, "which is", powGrad == (4.0, 2.772588722239781) ? "correct" : "incorrect")

let maxGrad = gradient(at: 1, 2, in: max)
print("max gradient: ", maxGrad, "which is", maxGrad == (0.0, 1.0) ? "correct" : "incorrect")

let absGrad = gradient(at: 2, in: abs)
print("abs gradient: ", absGrad, "which is", absGrad == 1.0 ? "correct" : "incorrect")

let sqrtGrad = gradient(at: 4, in: sqrt)
print("sqrt gradient: ", sqrtGrad, "which is", sqrtGrad == 0.25 ? "correct" : "incorrect")
pow gradient:  (4.0, 2.772588722239781) which is correct
max gradient:  (0.0, 1.0) which is correct
abs gradient:  1.0 which is correct
sqrt gradient:  0.25 which is correct

O erro do compilador que alerta sobre a necessidade de algo assim é: A Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files

KeyPath

KeyPath (get ou set) não funciona imediatamente, mas, mais uma vez, existem algumas extensões que você pode adicionar e, em seguida, usar uma sintaxe alternativa. Aqui está:

https://github.com/tensorflow/swift/issues/530#issuecomment-687400701

Esta solução alternativa é um pouco mais feia do que as outras. Ele só funciona para objetos personalizados, que devem estar em conformidade com Differentiable e AdditiveArithmetic. Você precisa adicionar um membro .tmp e uma função .read() e usar o membro .tmp como armazenamento intermediário ao fazer KeyPath subscrito de KeyPath (há um exemplo no código vinculado). KeyPath conjuntos de subscritos do KeyPath funcionam de maneira bastante simples com uma função .write() .