หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

ความแตกต่างที่กำหนดเอง

ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub

บทช่วยสอนนี้จะแสดงวิธีกำหนดอนุพันธ์ที่กำหนดเองของคุณเองทำการผ่าตัดอนุพันธ์และใช้ API การตรวจสอบการไล่ระดับสีของคุณเองใน Swift เพียง 5 บรรทัด

การประกาศอนุพันธ์ที่กำหนดเอง

คุณสามารถกำหนดอนุพันธ์ที่กำหนดเองสำหรับฟังก์ชัน Swift ใด ๆ ที่มีพารามิเตอร์และผลลัพธ์ที่แตกต่างกัน เมื่อทำเช่นนั้นคุณยังสามารถนำเข้าฟังก์ชัน C และทำให้แตกต่างได้

import Glibc

func sillyExp(_ x: Float) -> Float {
    let 𝑒 = Float(M_E)
    print("Taking 𝑒(\(𝑒)) to the power of \(x)!")
    return pow(𝑒, x)
}

@derivative(of: sillyExp)
func sillyDerivative(_ x: Float) -> (value: Float, pullback: (Float) -> Float) {
    let y = sillyExp(x)
    return (value: y, pullback: { v in v * y })
}

print("exp(3) =", sillyExp(3))
print("𝛁exp(3) =", gradient(of: sillyExp)(3))
Taking 𝑒(2.7182817) to the power of 3.0!
exp(3) = 20.085535
Taking 𝑒(2.7182817) to the power of 3.0!
𝛁exp(3) = 20.085535

หยุดการขยายพันธุ์อนุพันธ์

ที่เรียกกันทั่วไปว่า "หยุดการไล่ระดับสี" ในกรณีการใช้งาน withoutDerivative(at:) เมธอด withoutDerivative(at:) จะหยุดอนุพันธ์จากการแพร่กระจาย

นอกจากนี้ withoutDerivative(at:) บางครั้งสามารถช่วยคอมไพเลอร์ Swift ในการระบุสิ่งที่จะไม่ทำให้แตกต่างและสร้างอนุพันธ์ที่มีประสิทธิภาพมากขึ้น เมื่อตรวจพบว่าอนุพันธ์ของฟังก์ชันจะเป็นศูนย์เสมอคอมไพเลอร์ Swift จะส่งคำเตือน โดยชัดแจ้งโดยใช้ withoutDerivative(at:) เตือนนั้น

let x: Float = 2.0
let y: Float = 3.0
gradient(at: x, y) { x, y in
    sin(sin(sin(x))) + withoutDerivative(at: cos(cos(cos(y))))
}
▿ 2 elements

  - .0 : -0.18009877
  - .1 : 0.0

การผ่าตัดอนุพันธ์

Method withDerivative(_:) ทำให้การดำเนินการโดยพลการ (รวมถึงการกลายพันธุ์) รันบนเกรเดียนต์ที่ค่าระหว่าง backpropagation ของฟังก์ชันล้อมรอบ

ใช้สิ่งนี้เพื่อแก้ไขข้อบกพร่องหรือทำการปรับแต่งการทดลองเพื่อ backpropagation

ทำงานได้ทุกที่

API การสร้างความแตกต่างทั้งหมดที่จัดเตรียมโดยไลบรารีมาตรฐานได้รับการกำหนดโดยทั่วไปในทุกประเภทที่สอดคล้องกับโปรโตคอลที่ Differentiable : Float , Double , Float80 , เวกเตอร์ SIMD และแม้แต่ประเภทของคุณเอง!

อ่านเอกสารทางเทคนิค ประเภทที่แตกต่าง สำหรับข้อมูลเชิงลึกเพิ่มเติมเกี่ยวกับโปรโตคอลที่ Differentiable

var x: Float = 30
gradient(at: x) { x -> Float in
    // Print the partial derivative with respect to the result of `sin(x)`.
    let a = sin(x).withDerivative { print("∂+/∂sin = \($0)") } 
    // Force the partial derivative with respect to `x` to be `0.5`.
    let b = log(x.withDerivative { (dx: inout Float) in
        print("∂log/∂x = \(dx), but rewritten to 0.5");
        dx = 0.5
    })
    return a + b
}
∂log/∂x = 0.033333335, but rewritten to 0.5
∂+/∂sin = 1.0

0.65425146

ใช้ในโมดูลเครือข่ายประสาทเทียม

เช่นเดียวกับวิธีที่เราใช้ในฟังก์ชัน Float แบบธรรมดาเราสามารถใช้มันในแอปพลิเคชันตัวเลขใด ๆ เช่นเครือข่ายประสาทเทียมต่อไปนี้ที่สร้างขึ้นโดยใช้ Swift for TensorFlow Deep Learning Library

import TensorFlow

struct MLP: Layer {
    var layer1 = Dense<Float>(inputSize: 2, outputSize: 10, activation: relu)
    var layer2 = Dense<Float>(inputSize: 10, outputSize: 1, activation: relu)
    
    @differentiable
    func callAsFunction(_ input: Tensor<Float>) -> Tensor<Float> {
        let h0 = layer1(input).withDerivative { print("∂L/∂layer1 =", $0) }
        return layer2(h0)
    }
}

var classifier = MLP()
let optimizer = SGD(for: classifier, learningRate: 0.02)

let x: Tensor<Float> = [[0, 0], [0, 1], [1, 0], [1, 1]]
let y: Tensor<Float> = [0, 1, 1, 0]

for _ in 0..<10 {
    let 𝛁model = gradient(at: classifier) { classifier -> Tensor<Float> in
        let ŷ = classifier(x).withDerivative { print("∂L/∂ŷ =", $0) }
        let loss = (ŷ - y).squared().mean()
        print("Loss: \(loss)")
        return loss
    }
    optimizer.update(&classifier, along: 𝛁model)
}
Loss: 0.33274758
∂L/∂ŷ = [[       -0.25],
 [  -0.0162234],
 [-0.105033934],
 [ 0.094616234]]
∂L/∂layer1 = [[          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [-0.0056895884,    0.01067573,  -0.008025628, -0.0065082344,  0.0038597002,  0.0076597244,
   -0.008850923,  -0.008645675, 0.00091177685,  -0.003947232],
 [  -0.03683567,    0.06911708,  -0.051959712,   -0.04213577,   0.024988564,   0.049590774,
    -0.05730286,   -0.05597404,  0.0059030475,  -0.025555266],
 [   0.03318216,  -0.062261757,   0.046806134,    0.03795657,  -0.022510095,  -0.044672154,
    0.051619325,   0.050422303,  -0.005317559,   0.023020588]]
Loss: 0.33206546
∂L/∂ŷ = [[ -0.24949601],
 [-0.017163903],
 [ -0.10388964],
 [  0.09343198]]
∂L/∂layer1 = [[ -0.087382756,    0.16425455,   -0.12343603,  -0.099958554,   0.059345044,    0.11769368,
    -0.13612662,     -0.132847,   0.014168547,  -0.060705103],
 [ -0.006011435,   0.011299776,  -0.008491694,  -0.006876578,  0.0040826006,   0.008096653,
   -0.009364735,  -0.009139116, 0.00097471516,  -0.004176165],
 [    -0.036386,   0.068395264,  -0.051398512,   -0.04162254,   0.024711158,    0.04900741,
   -0.056682855,  -0.055317223,   0.005899754,  -0.025277482],
 [   0.03272334,  -0.061510514,   0.046224676,   0.037432764,  -0.022223702,  -0.044074263,
     0.05097709,   0.049748924,  -0.005305878,    0.02273302]]
Loss: 0.32871217
∂L/∂ŷ = [[  -0.2439641],
 [-0.013019085],
 [ -0.09845731],
 [   0.0965938]]
∂L/∂layer1 = [[ -0.085335776,    0.16068377,    -0.1207117,   -0.09762091,   0.058017734,    0.11498504,
    -0.13311927,   -0.12979509,   0.013993774,  -0.059361998],
 [ -0.004553923,    0.00857485, -0.0064417506, -0.0052095163,  0.0030961023,  0.0061361487,
  -0.0071038776,  -0.006926483,  0.0007467743, -0.0031678386],
 [  -0.03443921,    0.06484762,  -0.048715975,  -0.039397158,   0.023414386,    0.04640485,
   -0.053723335,   -0.05238178,  0.0056475084,  -0.023956895],
 [  0.033787373,   -0.06362024,   0.047793925,   0.038651485,   -0.02297122,  -0.045526538,
     0.05270651,   0.051390346, -0.0055406173,    0.02350346]]
Loss: 0.32567233
∂L/∂ŷ = [[  -0.2387768],
 [-0.009471901],
 [ -0.09363252],
 [  0.09900099]]
∂L/∂layer1 = [[  -0.08339106,    0.15736054,   -0.11814025,    -0.0954092,    0.05677393,    0.11244918,
    -0.13030054,   -0.12691614,   0.013847596,  -0.058072347],
 [-0.0033079926,  0.0062422454,  -0.004686438,  -0.003784733,  0.0022521326,   0.004460682,
  -0.0051688175,  -0.005034564,  0.0005493124,  -0.002303639],
 [  -0.03270048,   0.061706427,  -0.046326816,    -0.0374132,   0.022262992,    0.04409515,
   -0.051095277,  -0.049768142,  0.0054301145,  -0.022772146],
 [  0.034575377,    -0.0652444,    0.04898299,   0.039558303,  -0.023539452,   -0.04662337,
    0.054024853,   0.052621625,  -0.005741453,     0.0240778]]
Loss: 0.3228815
∂L/∂ŷ = [[ -0.23389274],
 [-0.006436102],
 [  -0.0893316],
 [  0.10076761]]
∂L/∂layer1 = [[  -0.08153905,    0.15425265,  -0.115705006,   -0.09331068,   0.055603556,    0.11006514,
     -0.1276478,   -0.12419132,   0.013724609,   -0.05683275],
 [-0.0022437365,    0.00424462,  -0.003183892,   -0.00256766,   0.001530061,   0.003028698,
   -0.003512526, -0.0034174128, 0.00037766452, -0.0015638851],
 [ -0.031142538,   0.058914337,   -0.04419168,  -0.035638522,    0.02123689,    0.04203762,
    -0.04875304,  -0.047432892,  0.0052418956,  -0.021706361],
 [   0.03512933,   -0.06645641,   0.049848992,   0.040200878,  -0.023955585,  -0.047419176,
     0.05499429,   0.053505134, -0.0059129503,   0.024485158]]
Loss: 0.32029176
∂L/∂ŷ = [[  -0.22927606],
 [-0.0038376972],
 [  -0.08548254],
 [  0.101991385]]
∂L/∂layer1 = [[   -0.07977117,     0.15133253,   -0.113391355,    -0.09131406,     0.05449789,
      0.10781483,    -0.12514149,    -0.12160411,    0.013620339,     -0.0556399],
 [ -0.0013352358,    0.002533053,  -0.0018979814,  -0.0015284444,   0.0009122034,
    0.0018046397,   -0.002094659,   -0.002035449,  0.00022798167, -0.00093131873],
 [  -0.029741623,    0.056422323,   -0.042276464,   -0.034045234,    0.020318815,
      0.04019733,   -0.046657346,   -0.045338478,    0.005078163,     -0.0207446],
 [   0.035485484,    -0.06731891,     0.05044112,    0.040620234,   -0.024242895,
    -0.047960456,    0.055668063,     0.05409449,  -0.0060588853,    0.024750907]]
Loss: 0.31786746
∂L/∂ŷ = [[  -0.22489586],
 [-0.0016133115],
 [   -0.0820235],
 [   0.10275632]]
∂L/∂layer1 = [[   -0.07807983,     0.14857662,   -0.111186594,    -0.08940941,    0.053449426,
      0.10568272,   -0.122764714,    -0.11914019,    0.013531086,    -0.05449069],
 [ -0.0005601129,   0.0010658281, -0.00079760735,  -0.0006413868,  0.00038342446,
   0.00075812486,  -0.0008806641, -0.00085466326,  9.7066506e-05, -0.00039089404],
 [  -0.028477093,     0.05418852,    -0.04055172,   -0.032609195,    0.019493952,
      0.03854436,    -0.04477446,   -0.043452535,    0.004935026,    -0.01987372],
 [   0.035675157,    -0.06788558,    0.050801847,    0.040851716,   -0.024421375,
    -0.048287094,     0.05609196,    0.054435894,  -0.0061824373,    0.024897136]]
Loss: 0.31558186
∂L/∂ŷ = [[ -0.22072542],
 [0.0002914667],
 [ -0.07890124],
 [ 0.103134096]]
∂L/∂layer1 = [[  -0.076458275,     0.14596471,     -0.1090796,    -0.08758796,    0.052451674,
      0.10365538,    -0.12050286,    -0.11678703,    0.013453777,   -0.053382203],
 [ 0.00010096274, -0.00019274562,  0.00014403902,  0.00011565942,  -6.926215e-05,
  -0.00013687638,  0.00015912336,  0.00015421664,  -1.776564e-05,   7.049091e-05],
 [  -0.027331028,     0.05217703,   -0.038991954,    -0.03130948,    0.018749548,
     0.037052996,    -0.04307535,    -0.04174708,    0.004809232,    -0.01908218],
 [    0.03572518,    -0.06820211,    0.050967515,    0.040925533,    -0.02450808,
    -0.048433047,     0.05630504,    0.054568816,  -0.0062862863,     0.02494287]]
Loss: 0.31341466
∂L/∂ŷ = [[ -0.21674162],
 [0.0019231141],
 [ -0.07607014],
 [  0.10318613]]
∂L/∂layer1 = [[  -0.074900545,     0.14347956,    -0.10706067,    -0.08584198,     0.05149903,
      0.10172125,     -0.1183433,    -0.11453373,    0.013385869,    -0.05231174],
 [  0.0006645807,  -0.0012730714,   0.0009499324,   0.0007616623, -0.00045694277,
   -0.0009025565,   0.0010500414,   0.0010162396, -0.00011877069,  0.00046415377],
 [  -0.026287958,    0.050357237,   -0.037575245,   -0.030128093,    0.018074695,
      0.03570126,   -0.041535128,   -0.040198077,   0.0046980586,   -0.018359931],
 [   0.035658576,     -0.0683076,    0.050969336,    0.040867567,   -0.024517607,
    -0.048427347,    0.056340758,      0.0545271,  -0.0063727307,     0.02490452]]
Loss: 0.31138343
∂L/∂ŷ = [[ -0.21300167],
 [0.0033213794],
 [ -0.07349101],
 [  0.10296476]]
∂L/∂layer1 = [[   -0.07342794,     0.14115742,    -0.10515943,   -0.084195204,    0.050604995,
      0.09990652,    -0.11631727,    -0.11241147,    0.013330075,   -0.051295396],
 [  0.0011449772,   -0.002201097,   0.0016397729,   0.0013128734, -0.00078909425,
   -0.0015578632,   0.0018137594,   0.0017528555, -0.00020785864,   0.0007998598],
 [  -0.025334511,     0.04870291,   -0.036282685,   -0.029049493,    0.017460015,
     0.034470297,   -0.040132426,   -0.038784824,    0.004599216,   -0.017698219],
 [    0.03549498,    -0.06823534,    0.050833948,    0.040699866,   -0.024462396,
    -0.048294697,    0.056227632,    0.054339573,   -0.006443743,    0.024796136]]

การคำนวณการเปิดใช้งานใหม่ระหว่างการขยายภาพย้อนกลับเพื่อบันทึกหน่วยความจำ (การตรวจสอบ)

การตรวจจุดเป็นเทคนิคดั้งเดิมในการสร้างความแตกต่างอัตโนมัติในโหมดย้อนกลับเพื่อประหยัดหน่วยความจำ แทนที่จะบันทึกค่ากลางขนาดใหญ่ในการคำนวณดั้งเดิมสำหรับการคำนวณอนุพันธ์ค่ากลางจะถูกคำนวณใหม่แทนตามความจำเป็นในระหว่างการขยายภาพย้อนกลับ

เทคนิคนี้ได้รับการยอมรับในห้องสมุดการเรียนรู้เชิงลึกสมัยใหม่เช่นกัน ใน Swift API withRecomputationInPullbacks(_:) ช่วยให้คุณสามารถควบคุมสิ่งที่จะคำนวณซ้ำในระหว่างการขยายภาพกลับและสามารถใช้ได้กับประเภทที่ Differentiable ทั้งหมด

แต่วันนี้ขอให้เราเรียนรู้วิธีกำหนด API การตรวจสอบการไล่ระดับสีของเราเองตั้งแต่เริ่มต้นโดยใช้โค้ดเพียงไม่กี่บรรทัด

API การตรวจสอบการไล่ระดับสีของเรา

เราสามารถกำหนด API การตรวจสอบการไล่ระดับสีของเราเอง makeRecomputedInGradient(_:) ในแง่ของฟังก์ชันไลบรารีมาตรฐาน differentiableFunction(from:) ซึ่งเป็นชวเลขสำหรับสร้างฟังก์ชันที่แตกต่างได้โดยตรงจากฟังก์ชันอนุพันธ์ (เรียกอีกอย่างว่า "vector-Jacobian products (VJP) ฟังก์ชัน ")

ดังที่เราเคยเห็นมาก่อนฟังก์ชันอนุพันธ์จะส่งคืนทูเพิลของผลลัพธ์ของฟังก์ชันดั้งเดิมและการปิดแบบดึงกลับ เราคืนค่า original(x) ใน value: และเรียก pullback(at:in:) บน original เพื่อประเมินฟังก์ชันเดิมอีกครั้งและรับการดึงกลับ

/// Given a differentiable function, returns the same differentiable function except when
/// derivatives of this function are being computed. In that case, values in the original function needed
/// for computing the derivatives will be recomputed, instead of being captured by the differential or pullback.
///
/// - Parameter body: The body of the differentiable function.
/// - Returns: The same differentiable function whose derivatives, when computed, will recompute
///   some values from the original function.
func makeRecomputedInGradient<T: Differentiable, U: Differentiable>(
    _ original: @escaping @differentiable (T) -> U
) -> @differentiable (T) -> U {
    return differentiableFunction { x in
        (value: original(x), pullback: { v in pullback(at: x, in: original)(v) })
    }
}

ตรวจสอบว่าใช้งานได้

let input: Float = 10.0
print("Running original computation...")

// Differentiable multiplication with checkpointing.
let square = makeRecomputedInGradient { (x: Float) -> Float in
    print("  Computing square...")
    return x * x
}

// Differentiate `f(x) = (cos(x))^2`.
let (output, backprop) = valueWithPullback(at: input) { input -> Float in
    return square(cos(input))
}
print("Running backpropagation...")
let grad = backprop(1)
print("Gradient = \(grad)")
Running original computation...
  Computing square...
Running backpropagation...
  Computing square...
Gradient = -0.9129453

ขยายไปยังโมดูลเครือข่ายประสาทเทียม

ในตัวอย่างนี้เรากำหนดโครงข่ายประสาทเทียมแบบง่ายๆ

struct Model: Layer {
    var conv = Conv2D<Float>(filterShape: (5, 5, 3, 6))
    var maxPool = MaxPool2D<Float>(poolSize: (2, 2), strides: (2, 2))
    var flatten = Flatten<Float>()
    var dense = Dense<Float>(inputSize: 36 * 6, outputSize: 10)

    @differentiable
    func call(_ input: Tensor<Float>) -> Tensor<Float> {
        return input.sequenced(through: conv, maxPool, flatten, dense)
    }
}

เราต้องการให้การเปิดใช้งานในชั้นบิด ( conv ) ได้รับการคำนวณใหม่ในระหว่างการแพร่กระจายย้อนกลับ อย่างไรก็ตามการใช้ makeRecomputedInGradient(_:) อาจทำให้โค้ดผลลัพธ์ดูยุ่งยากโดยเฉพาะอย่างยิ่งเมื่อเราต้องการใช้เลเยอร์ตามลำดับโดยใช้ sequenced(in:through:_:_:_:_:)

input.sequenced(in: context, through: conv, maxPool, flatten, dense)

เหตุใดเราจึงไม่กำหนด ประเภทเลเยอร์พิเศษ ที่ห่อหุ้มเลเยอร์และทำให้การเปิดใช้งานถูกคำนวณใหม่ในระหว่างการขยายภาพกลับ ลงมือทำกันเลย

ขั้นแรกเรากำหนด makeRecomputedInGradient(_:) ที่ใช้ฟังก์ชันไบนารี

// Same as the previous `makeRecomputedInGradient(_:)`, except it's for binary functions.
func makeRecomputedInGradient<T: Differentiable, U: Differentiable, V: Differentiable>(
    _ original: @escaping @differentiable (T, U) -> V
) -> @differentiable (T, U) -> V {
    return differentiableFunction { x, y in
        (value: original(x, y), pullback: { v in pullback(at: x, y, in: original)(v) })
    }
}

จากนั้นเรากำหนดเลเยอร์ทั่วไป ActivationDiscarding<Wrapped>

import TensorFlow

/// A layer wrapper that makes the underlying layer's activations be discarded during application
/// and recomputed during backpropagation.
struct ActivationDiscarding<Wrapped: Layer>: Layer {
    /// The wrapped layer.
    var wrapped: Wrapped

    @differentiable
    func callAsFunction(_ input: Wrapped.Input) -> Wrapped.Output {
        let apply = makeRecomputedInGradient { (layer: Wrapped, input: Input) -> Wrapped.Output in
            print("    Applying \(Wrapped.self) layer...")
            return layer(input)
        }
        return apply(wrapped, input)
    }
}

ในที่สุดเราสามารถเพิ่มเมธอดในทุกเลเยอร์ที่ส่งคืนเลเยอร์เดียวกันยกเว้นการเปิดใช้งานจะถูกละทิ้งระหว่างแอปพลิเคชันและคำนวณใหม่ในระหว่างการขยายภาพกลับ

extension Layer {
    func discardingActivations() -> ActivationDiscarding<Self> {
        return ActivationDiscarding(wrapped: self)
    }
}

ย้อนกลับไปในแบบจำลองสิ่งที่เราต้องเปลี่ยนคือการรวมเลเยอร์คอนโวลูชั่นไว้ในเลเยอร์การคำนวณการเปิดใช้งานใหม่

var conv = Conv2D<Float>(filterShape: (5, 5, 3, 6)).discardingActivations()

ตอนนี้ใช้ในรุ่น!

struct Model: Layer {
    var conv = Conv2D<Float>(filterShape: (5, 5, 3, 6)).discardingActivations()
    var maxPool = MaxPool2D<Float>(poolSize: (2, 2), strides: (2, 2))
    var flatten = Flatten<Float>()
    var dense = Dense<Float>(inputSize: 36 * 6, outputSize: 10)

    @differentiable
    func callAsFunction(_ input: Tensor<Float>) -> Tensor<Float> {
        return input.sequenced(through: conv, maxPool, flatten, dense)
    }
}

เมื่อเราเรียกใช้ลูปการฝึกอบรมเราจะเห็นว่าการเปิดใช้งานเลเยอร์คอนโวลูชั่นถูกคำนวณสองครั้ง: หนึ่งครั้งในระหว่างการใช้เลเยอร์และอีกครั้งในระหว่างการขยายภาพย้อนกลับ

// Use random training data.
let x = Tensor<Float>(randomNormal: [10, 16, 16, 3])
let y = Tensor<Int32>(rangeFrom: 0, to: 10, stride: 1)

var model = Model()
let opt = SGD(for: model)

for i in 1...5 {
    print("Starting training step \(i)")
    print("  Running original computation...")
    let (logits, backprop) = model.appliedForBackpropagation(to: x)
    let (loss, dL_dŷ) = valueWithGradient(at: logits) { logits in
        softmaxCrossEntropy(logits: logits, labels: y)
    }
    print("  Loss: \(loss)")
    print("  Running backpropagation...")
    let (dL_dθ, _) = backprop(dL_dŷ)
    
    opt.update(&model, along: dL_dθ)
}
Starting training step 1
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 3.3274093
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 2
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.8839695
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 3
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.554683
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 4
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.2845771
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 5
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.0549595
  Running backpropagation...
    Applying Conv2D<Float> layer...

เช่นเดียวกับที่กำหนดไลบรารีการเขียนโปรแกรมที่แตกต่างกันทั่วไปสำหรับโดเมนต่างๆได้ง่ายมาก