Farklılaştırılabilir Swift, kullanılabilirlik açısından uzun bir yol kat etti. İşte hala biraz açık olmayan kısımlar hakkında bir uyarı. İlerleme devam ettikçe bu kılavuz giderek küçülecek ve özel sözdizimine ihtiyaç duymadan türevlenebilir kod yazabileceksiniz.
Döngüler
Döngüler türevlenebilir, bilinmesi gereken tek bir ayrıntı var. Döngüyü yazarken, biti, döngünün ne olduğunu belirttiğiniz yere withoutDerivative(at:)
ile sarın.
var a: [Float] = [1,2,3]
Örneğin:
for _ in a.indices
{}
olur
for _ in withoutDerivative(at: a.indices)
{}
veya:
for _ in 0..<a.count
{}
olur
for _ in 0..<withoutDerivative(at: a.count)
{}
Array.count
üyesinin diziye göre türevine katkısı olmadığından bu gereklidir. Yalnızca dizideki gerçek öğeler türevine katkıda bulunur.
Üst sınır olarak manuel olarak bir tamsayı kullandığınız bir döngünüz varsa, withoutDerivative(at:)
kullanmanıza gerek yoktur:
let iterations: Int = 10
for _ in 0..<iterations {} //this is fine as-is.
Haritala ve Azalt
map
ve reduce
tam olarak alışık olduğunuz gibi çalışan özel türevlenebilir sürümlere sahiptir:
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
Dizi alt simge kümeleri
Dizi alt simge kümeleri ( array[0] = 0
) kutudan çıktığı haliyle farklılaştırılamaz, ancak şu uzantıyı yapıştırabilirsiniz:
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
})
}
}
ve sonra geçici çözüm sözdizimi şu şekildedir:
var b: [Float] = [1,2,3]
bunun yerine:
b[0] = 17
bunu yaz:
b.updated(at: 0, with: 17)
Çalıştığından emin olalım:
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])
Bu geçici çözüm olmadan alacağınız hata: Differentiation of coroutine calls is not yet supported
. Bu geçici çözümü gereksiz hale getirme konusundaki ilerlemeyi görmek için bağlantı burada: https://bugs.swift.org/browse/TF-1277 (bir dizi yaptığınızda perde arkasında adı verilen Array.subscript._modify'dan bahsediyor) alt simge kümesi).
Float
<-> Double
dönüşüm
Float
ve Double
arasında geçiş yapıyorsanız yapıcıları zaten ayırt edilemez. Burada Float
Double
diferansiyel olarak gitmenizi sağlayacak bir fonksiyon var.
(Aşağıdaki kodda Float
ve Double
değiştirin; Double
Float
dönüşen bir işleve sahip olursunuz.)
Diğer gerçek Sayısal türler için benzer dönüştürücüler yapabilirsiniz.
@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)
}
İşte bir örnek kullanım:
@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
Aşkın ve diğer işlevler (sin, cos, abs, max)
Float
ve Double
için birçok transandantal ve diğer yaygın yerleşik işlevler zaten farklılaştırılabilir hale getirildi. Double
için Float
daha az sayıda var. Bazıları her ikisi için de mevcut değil. Halihazırda sağlanmadığı takdirde, ihtiyacınız olanı nasıl yapacağınız konusunda size fikir verecek birkaç manüel türev tanımı aşağıda verilmiştir:
pow (türev açıklaması için bağlantıya bakın)
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)
}
maksimum
@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)
}
karın kasları
@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 (türev açıklaması için bağlantıya bakın)
@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)
}
Bunların işe yarayıp yaramadığını kontrol edelim:
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
Bunun gibi bir şeyin gerekliliği konusunda sizi uyaran derleyici hatası şudur: Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files
KeyPath
aboneliği
KeyPath
aboneliği (alma veya ayarlama) kutudan çıktığı gibi çalışmaz, ancak bir kez daha ekleyebileceğiniz bazı uzantılar vardır ve ardından geçici çözüm sözdizimini kullanabilirsiniz. İşte burada:
https://github.com/tensorflow/swift/issues/530#issuecomment-687400701
Bu geçici çözüm diğerlerinden biraz daha çirkin. Yalnızca Diferansiyellenebilir ve EklemeliAritmetik'e uyması gereken özel nesneler için çalışır. Bir .tmp
üyesi ve bir .read()
işlevi eklemeniz gerekir ve KeyPath
alt simge alımını yaparken .tmp
üyesini ara depolama olarak kullanırsınız (bağlantılı kodda bir örnek vardır). KeyPath
alt simge kümeleri .write()
işleviyle oldukça basit bir şekilde çalışır.