ה-Differentiable Swift עברה דרך ארוכה מבחינת שימושיות. הנה הסבר על החלקים שעדיין קצת לא ברורים. ככל שההתקדמות תימשך, מדריך זה ילך וקטן, ותוכל לכתוב קוד שניתן להבדיל ללא צורך בתחביר מיוחד.
לולאות
לולאות ניתנות להבדלה, יש רק פרט אחד שצריך לדעת עליו. כשאתה כותב את הלולאה, עטוף את הסיביות במקום שבו אתה מציין מה אתה עובר בלולאה 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
subscript gets (יש דוגמה בקוד המקושר). ערכות מנוי KeyPath
עובדות די פשוט עם פונקציית .write()
.