ขอบที่คมชัดใน Differentiable Swift

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 บางอย่างก็ไม่สามารถใช้ได้เช่นกัน ต่อไปนี้เป็นคำจำกัดความอนุพันธ์ด้วยตนเองบางส่วนเพื่อให้คุณมีแนวคิดในการสร้างสิ่งที่คุณต้องการ ในกรณีที่ยังไม่ได้ระบุไว้:

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