Spigoli vivi in ​​Differentiable Swift

Differenziabile Swift ha fatto molta strada in termini di usabilità. Ecco un avvertimento sulle parti che sono ancora un po' poco ovvie. Man mano che i progressi continuano, questa guida diventerà sempre più piccola e sarai in grado di scrivere codice differenziabile senza bisogno di una sintassi speciale.

Loop

I cicli sono differenziabili, c'è solo un dettaglio da sapere. Quando scrivi il ciclo, avvolgi la parte in cui specifichi cosa stai eseguendo il withoutDerivative(at:)

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

Per esempio:

for _ in a.indices 
{}

diventa

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

O:

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

diventa

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

Ciò è necessario perché il membro Array.count non contribuisce alla derivata rispetto all'array. Solo gli elementi effettivi dell'array contribuiscono alla derivata.

Se hai un ciclo in cui usi manualmente un numero intero come limite superiore, non è necessario usarlo withoutDerivative(at:) :

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

Mappa e riduci

map e reduce hanno versioni speciali differenziabili che funzionano esattamente come quello a cui sei abituato:

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

Insiemi di pedici di array

Gli insiemi di pedici di array ( array[0] = 0 ) non sono differenziabili immediatamente, ma puoi incollare questa estensione:

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 quindi la sintassi della soluzione alternativa è questa:

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

Invece di questo:

b[0] = 17

Scrivi questo:

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

Assicuriamoci che funzioni:

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

L'errore che riceverai senza questa soluzione alternativa è che Differentiation of coroutine calls is not yet supported . Ecco il collegamento per vedere i progressi nel rendere superflua questa soluzione alternativa: https://bugs.swift.org/browse/TF-1277 (si parla di Array.subscript._modify, che è ciò che viene chiamato dietro le quinte quando si esegue un array insieme di pedici).

Float <-> Conversioni Double

Se stai passando da Float a Double , i loro costruttori non sono già differenziabili. Ecco una funzione che ti permetterà di passare da Float a Double in modo differenziato.

(Cambia Float e Double nel codice seguente e avrai una funzione che converte da Double a Float .)

Puoi creare convertitori simili per qualsiasi altro tipo numerico reale.

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

Ecco un esempio di utilizzo:

@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

Funzioni trascendentali e altre (sin, cos, abs, max)

Molti trascendentali e altre funzioni integrate comuni sono già stati resi differenziabili per Float e Double . Ce ne sono meno per Double che per Float . Alcuni non sono disponibili per nessuno dei due. Quindi ecco alcune definizioni manuali di derivate per darti un'idea di come realizzare ciò di cui hai bisogno, nel caso in cui non sia già fornito:

pow (vedi link per la spiegazione della derivata)

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

massimo

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

addominali

@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 (vedi link per la spiegazione della derivata)

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

Controlliamo che funzionino:

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

L'errore del compilatore che avvisa della necessità di qualcosa di simile è: Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files

Sottoscrizione KeyPath

La sottoscrizione KeyPath (get o set) non funziona immediatamente, ma ancora una volta è possibile aggiungere alcune estensioni e quindi utilizzare una sintassi alternativa. Ecco qui:

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

Questa soluzione alternativa è un po’ più brutta delle altre. Funziona solo per oggetti personalizzati, che devono essere conformi a Differentiable e AdditiveArithmetic. Devi aggiungere un membro .tmp e una funzione .read() e utilizzare il membro .tmp come memoria intermedia quando si ottiene il subscript KeyPath (c'è un esempio nel codice collegato). I set di pedici KeyPath funzionano in modo abbastanza semplice con una funzione .write() .