سوئیفت متمایز از نظر قابلیت استفاده راه طولانی را پیموده است. در اینجا یک سرفصل در مورد بخش هایی است که هنوز کمی نامشخص هستند. با ادامه پیشرفت، این راهنما کوچکتر و کوچکتر می شود و شما قادر خواهید بود بدون نیاز به نحو خاص، کدهای متمایز بنویسید.
حلقه ها
حلقه ها قابل تمایز هستند، فقط یک جزئیات وجود دارد که باید در مورد آن بدانید. هنگامی که حلقه را می نویسید، بیت را در جایی که مشخص می کنید چه چیزی را در آن حلقه می زنید 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()
کار می کنند.