Diferansiyellenebilir Swift'de keskin kenarlar

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.