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()
.