Bordes afilados en velocidad diferenciable

Swift diferenciable ha recorrido un largo camino en términos de usabilidad. Aquí hay un aviso sobre las partes que aún no son obvias. A medida que avanza, esta guía se hará cada vez más pequeña y podrá escribir código diferenciable sin necesidad de una sintaxis especial.

Bucles

Los bucles son diferenciables, solo hay un detalle que debe conocer. Cuando escriba el bucle, envuelva el bit donde especifica lo que está withoutDerivative(at:)

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

por ejemplo:

for _ in a.indices 
{}

se convierte en

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

o:

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

se convierte en

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

Esto es necesario porque el miembro Array.count no contribuye a la derivada con respecto a la matriz. Solo los elementos reales de la matriz contribuyen a la derivada.

Si tiene un bucle en el que usa manualmente un número entero como límite superior, no es necesario usar withoutDerivative(at:) :

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

Mapa y Reducir

map y reduce tienen versiones diferenciables especiales que funcionan exactamente como lo que estás acostumbrado:

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 subíndices de matriz

Los conjuntos de subíndices de array[0] = 0 ( array[0] = 0 ) no son diferenciables de fábrica, pero puede pegar esta extensión:

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
        })
    }
}

y luego la sintaxis de la solución alternativa es así:

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

en lugar de esto:

b[0] = 17

escribe esto:

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

Asegurémonos 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])

El error que obtendrá sin esta solución alternativa es que Differentiation of coroutine calls is not yet supported . Aquí está el enlace para ver el progreso para hacer que esta solución sea innecesaria: https://bugs.swift.org/browse/TF-1277 (habla de Array.subscript._modify, que es lo que se llama detrás de escena cuando haces una matriz conjunto de subíndices).

Conversiones Float <-> Double

Si está cambiando entre Float y Double , sus constructores aún no son diferenciables. Aquí hay una función que le permitirá pasar de un Float a un Double diferenciada.

(Cambie Float y Double en el siguiente código y obtendrá una función que convierte de Double a Float ).

Puede hacer convertidores similares para cualquier otro 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)
}

Aquí hay un ejemplo 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

Funciones trascendentales y otras (sin, cos, abs, max)

Muchos trascendentales y otras funciones integradas comunes ya se han hecho diferenciables para Float y Double . Hay menos para Double que para Float . Algunos no están disponibles para ninguno de los dos. Así que aquí hay algunas definiciones derivadas manuales para darle una idea de cómo hacer lo que necesita, en caso de que aún no se haya proporcionado:

pow (consulte el enlace para obtener una explicación 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)
}

abdominales

@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 el enlace para obtener una explicación 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)
}

Comprobemos que funcionan:

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

El error del compilador que le alerta sobre la necesidad de algo como esto es: La Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files

KeyPath

KeyPath subíndice de KeyPath (obtener o configurar) no funciona de inmediato, pero una vez más, hay algunas extensiones que puede agregar y luego usar una sintaxis alternativa. Aquí lo tienes:

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

Esta solución es un poco más fea que las demás. Solo funciona para objetos personalizados, que deben ajustarse a la aritmética diferenciable y aditiva. Tiene que agregar un miembro .tmp y una función .read() , y usa el miembro .tmp como almacenamiento intermedio cuando se obtiene el subíndice KeyPath (hay un ejemplo en el código vinculado). KeyPath conjuntos de subíndices de KeyPath funcionan de manera bastante simple con una función .write() .