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