Tepi tajam di Swift yang Dapat Dibedakan

Swift yang dapat dibedakan telah berkembang pesat dalam hal kegunaan. Berikut ini informasi awal tentang bagian-bagian yang masih sedikit belum jelas. Seiring kemajuan yang terus berlanjut, panduan ini akan menjadi semakin kecil, dan Anda akan dapat menulis kode yang dapat dibedakan tanpa memerlukan sintaksis khusus.

loop

Loop dapat dibedakan, hanya ada satu detail yang perlu diketahui. Saat Anda menulis perulangan, bungkus bit tempat Anda menentukan apa yang Anda perulangan withoutDerivative(at:)

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

Misalnya:

for _ in a.indices 
{}

menjadi

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

atau:

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

menjadi

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

Hal ini diperlukan karena anggota Array.count tidak berkontribusi pada turunan terhadap array. Hanya elemen sebenarnya dalam array yang berkontribusi pada turunannya.

Jika Anda memiliki loop di mana Anda secara manual menggunakan bilangan bulat sebagai batas atas, tidak perlu menggunakan withoutDerivative(at:) :

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

Petakan dan Kurangi

map dan reduce memiliki versi khusus yang dapat dibedakan yang berfungsi persis seperti yang biasa Anda lakukan:

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

Kumpulan subskrip array

Kumpulan subskrip array ( array[0] = 0 ) tidak dapat dibedakan secara langsung, tetapi Anda dapat menempelkan ekstensi ini:

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

dan kemudian sintaks solusinya seperti ini:

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

alih-alih ini:

b[0] = 17

tulis ini:

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

Mari kita pastikan itu berhasil:

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

Kesalahan yang akan Anda dapatkan tanpa solusi ini adalah Differentiation of coroutine calls is not yet supported . Berikut ini tautan untuk melihat kemajuan dalam membuat solusi ini tidak diperlukan: https://bugs.swift.org/browse/TF-1277 (ini membahas tentang Array.subscript._modify, yang disebut di balik layar saat Anda membuat array kumpulan subskrip).

Float <-> Konversi Double

Jika Anda beralih antara Float dan Double , konstruktornya belum dapat dibedakan. Berikut adalah fungsi yang memungkinkan Anda beralih dari Float ke Double secara berbeda.

(Ganti Float dan Double pada kode di bawah ini, dan Anda mendapatkan fungsi yang mengkonversi dari Double ke Float .)

Anda dapat membuat konverter serupa untuk tipe Numerik nyata lainnya.

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

Berikut contoh penggunaannya:

@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

Fungsi transendental dan lainnya (sin, cos, abs, max)

Banyak transendental dan fungsi bawaan umum lainnya telah dapat dibedakan untuk Float dan Double . Jumlahnya lebih sedikit untuk Double daripada Float . Beberapa juga tidak tersedia. Jadi, berikut adalah beberapa definisi turunan manual untuk memberi Anda gambaran tentang cara membuat apa yang Anda perlukan, jika hal itu belum disediakan:

pow (lihat tautan untuk penjelasan turunannya)

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

maks

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

abs

@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 (lihat tautan untuk penjelasan turunan)

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

Mari kita periksa apakah ini berfungsi:

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

Kesalahan kompiler yang mengingatkan Anda akan perlunya sesuatu seperti ini adalah: Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files

Berlangganan KeyPath

Berlangganan KeyPath (dapatkan atau setel) tidak langsung berfungsi, tetapi sekali lagi, ada beberapa ekstensi yang dapat Anda tambahkan, lalu gunakan sintaksis solusinya. Ini dia:

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

Solusi ini sedikit lebih buruk dibandingkan solusi lainnya. Ini hanya berfungsi untuk objek khusus, yang harus sesuai dengan Diferensiable dan AdditiveArithmetic. Anda harus menambahkan anggota .tmp dan fungsi .read() , dan Anda menggunakan anggota .tmp sebagai penyimpanan perantara saat melakukan pengambilan subskrip KeyPath (ada contoh dalam kode tertaut). Kumpulan subskrip KeyPath bekerja cukup sederhana dengan fungsi .write() .