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 avance, esta guía será 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 que conocer un detalle. Cuando escriba el bucle, ajuste el bit donde especifica lo que está repitiendo withoutDerivative(at:)
var a: [Float] = [1,2,3]
Por ejemplo:
for _ in a.indices
{}
se convierte
for _ in withoutDerivative(at: a.indices)
{}
o:
for _ in 0..<a.count
{}
se convierte
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. Sólo 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 usarlo withoutDerivative(at:)
:
let iterations: Int = 10
for _ in 0..<iterations {} //this is fine as-is.
Mapear y Reducir
map
y reduce
tienen versiones diferenciables especiales que funcionan exactamente como 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 matriz ( array[0] = 0
) no son diferenciables de fábrica, pero puedes 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 es la siguiente:
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 funciona:
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 es 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 sobre Array.subscript._modify, que es lo que se llama detrás de escena cuando haces una matriz conjunto de subíndices).
Float
<-> Conversiones Double
Si estás cambiando entre Float
y Double
, sus constructores aún no son diferenciables. Aquí hay una función que le permitirá pasar de Float
a Double
de manera diferenciada.
(Cambie Float
y Double
en el siguiente código y obtendrá una función que convierte de Double
a Float
).
Puede realizar 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)
}
A continuación se muestra 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)
Ya se han diferenciado muchas funciones trascendentales y otras funciones integradas comunes 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 de derivados manuales para darle una idea de cómo hacer lo que necesita, en caso de que aún no esté incluido:
pow (ver enlace para 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)
}
máximo
@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 (ver enlace para 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 estos 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 de la necesidad de algo como esto es: Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files
Subíndice KeyPath
Los subíndices KeyPath
(get o set) no funcionan 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. Debe agregar un miembro .tmp
y una función .read()
, y usar el miembro .tmp
como almacenamiento intermedio cuando obtiene el subíndice KeyPath
(hay un ejemplo en el código vinculado). Los conjuntos de subíndices KeyPath
funcionan de forma bastante sencilla con una función .write()
.