קצוות חדים ב-Differentiable Swift

ה-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() .