حواف حادة في سويفت قابلة للتمييز

لقد قطعت لغة 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

الدوال المتسامية وغيرها (sin، cos، abs، max)

لقد تم بالفعل جعل الكثير من الوظائف المتعالية وغيرها من الوظائف المضمنة الشائعة قابلة للتمييز بين Float و Double . هناك عدد أقل من Double من Float . بعضها غير متاح لأي منهما. لذا، إليك بعض التعريفات المشتقة يدويًا لتعطيك فكرة عن كيفية صنع ما تحتاجه، في حالة عدم توفره بالفعل:

الأسرى (انظر الرابط لشرح المشتقات)

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