لبه های تیز در سوئیفت متفاوت

سوئیفت متمایز از نظر قابلیت استفاده راه طولانی را پیموده است. در اینجا یک سرفصل در مورد بخش هایی است که هنوز کمی نامشخص هستند. با ادامه پیشرفت، این راهنما کوچکتر و کوچکتر می شود و شما قادر خواهید بود بدون نیاز به نحو خاص، کدهای متمایز بنویسید.

حلقه ها

حلقه ها قابل تمایز هستند، فقط یک جزئیات وجود دارد که باید در مورد آن بدانید. هنگامی که حلقه را می نویسید، بیت را در جایی که مشخص می کنید چه چیزی را در آن حلقه می زنید withoutDerivative(at:)

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

مثلا:

for _ in a.indices 
{}

تبدیل می شود

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

یا:

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

تبدیل می شود

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

این امر ضروری است زیرا عضو Array.count با توجه به آرایه به مشتق کمک نمی کند. فقط عناصر واقعی در آرایه به مشتق کمک می کنند.

اگر یک حلقه دارید که در آن به صورت دستی از یک عدد صحیح به عنوان کران بالایی استفاده می کنید، نیازی به استفاده از withoutDerivative(at:) نیست:

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

نقشه و کاهش

map و reduce دارای نسخه‌های متمایزپذیر ویژه‌ای هستند که دقیقاً مانند آنچه شما به آن عادت کرده‌اید کار می‌کنند:

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

مجموعه های زیرنویس آرایه

مجموعه‌های زیرنویس آرایه ( array[0] = 0 ) خارج از جعبه قابل تمایز نیستند، اما می‌توانید این پسوند را جای‌گذاری کنید:

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

و سپس دستور راه حل به این صورت است:

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

به جای این:

b[0] = 17

این را بنویس:

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

بیایید مطمئن شویم که کار می کند:

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

خطایی که بدون این راه حل دریافت خواهید کرد این است که Differentiation of coroutine calls is not yet supported . در اینجا پیوندی برای مشاهده پیشرفت در غیرضروری این راه حل وجود دارد: https://bugs.swift.org/browse/TF-1277 (در مورد Array.subscript._modify صحبت می کند، که در پشت صحنه زمانی که یک آرایه انجام می دهید صحبت می کند. مجموعه زیرنویس).

Float <-> تبدیل های Double

اگر بین Float و Double جابجا می‌شوید، سازنده‌های آن‌ها از قبل قابل تمایز نیستند. در اینجا تابعی وجود دارد که به شما امکان می‌دهد به طور متمایز از یک Float به یک Double بروید.

(در کد زیر Float و Double تغییر دهید و تابعی دارید که از Double به Float تبدیل می شود.)

می توانید مبدل های مشابهی را برای هر نوع عددی واقعی دیگر بسازید.

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

در اینجا یک مثال استفاده آمده است:

@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

ماورایی و توابع دیگر (سین، cos، abs، حداکثر)

بسیاری از ماورایی ها و دیگر توابع داخلی رایج قبلاً برای Float و Double قابل تمایز ساخته شده اند. تعداد کمتری برای Double از Float وجود دارد. برخی برای هیچکدام در دسترس نیستند. بنابراین در اینجا چند تعریف مشتق دستی وجود دارد تا به شما ایده دهد که چگونه آنچه را که نیاز دارید بسازید، در صورتی که قبلاً ارائه نشده باشد:

pow (برای توضیح مشتق به لینک مراجعه کنید)

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

حداکثر

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

عضلات شکم

@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 (برای توضیح مشتق به لینک مراجعه کنید)

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

بیایید بررسی کنیم که اینها کار می کنند:

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

خطای کامپایلر که به شما در مورد نیاز به چیزی شبیه به این هشدار می دهد این است: Expression is not differentiable. Cannot differentiate functions that have not been marked '@differentiable' and that are defined in other files

اشتراک KeyPath

اشتراک KeyPath (دریافت یا تنظیم) خارج از چارچوب کار نمی کند، اما یک بار دیگر، برخی از برنامه های افزودنی وجود دارد که می توانید اضافه کنید، و سپس از یک نحو راه حل استفاده کنید. ایناهاش:

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

این راه حل کمی زشت تر از بقیه است. این فقط برای اشیاء سفارشی کار می کند، که باید با Differentiable و AdditiveArithmetic مطابقت داشته باشند. شما باید یک عضو .tmp و یک تابع .read() اضافه کنید و از عضو .tmp به عنوان ذخیره سازی میانی هنگام انجام زیرنویس KeyPath استفاده می کنید (مثالی در کد پیوند داده شده وجود دارد). مجموعه های زیرنویس KeyPath به سادگی با یک تابع .write() کار می کنند.