Halaman ini diterjemahkan oleh Cloud Translation API.
Switch to English

Diferensiasi khusus

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub

Tutorial ini akan menunjukkan kepada Anda bagaimana mendefinisikan turunan khusus Anda sendiri, melakukan operasi turunan, dan menerapkan API pemeriksaan titik kemiringan Anda sendiri hanya dalam 5 baris Swift.

Mendeklarasikan turunan khusus

Anda dapat menentukan turunan khusus untuk fungsi Swift yang memiliki parameter dan hasil yang dapat dibedakan. Dengan melakukan itu, Anda bahkan dapat mengimpor fungsi C dan membuatnya terdiferensiasi.

 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

Hentikan derivatif agar tidak menyebar

Umumnya dikenal sebagai "stop gradient" dalam kasus penggunaan pembelajaran mesin, metode withoutDerivative(at:) menghentikan turunan dari propagasi.

Plus, withoutDerivative(at:) terkadang dapat membantu kompiler Swift dengan mengidentifikasi apa yang tidak perlu dibedakan dan menghasilkan derivasi yang lebih efisien. Ketika terdeteksi bahwa turunan dari suatu fungsi akan selalu nol, kompiler Swift akan menghasilkan peringatan. withoutDerivative(at:) eksplisit withoutDerivative(at:) membungkam peringatan itu.

 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

Operasi turunan

Metode withDerivative(_:) membuat operasi sewenang-wenang (termasuk mutasi) berjalan pada gradien pada nilai selama backpropagation fungsi melampirkan.

Gunakan ini untuk men-debug atau membuat penyesuaian eksperimental untuk backpropagation.

Ini bekerja di mana saja

Semua API diferensiasi yang disediakan oleh perpustakaan standar didefinisikan secara umum untuk semua jenis yang sesuai dengan protokol Differentiable : Float , Double , Float80 , vektor SIMD, dan bahkan tipe Anda sendiri!

Baca dokumen teknis Jenis yang Dapat Dibedakan untuk informasi lebih lanjut tentang protokol yang Dapat 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

Gunakan dalam modul jaringan saraf

Sama seperti bagaimana kita menggunakannya dalam fungsi Float sederhana, kita dapat menggunakannya dalam aplikasi numerik apa pun, seperti jaringan saraf berikut yang dibangun menggunakan 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.4281609
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.19194186],
 [-0.12774679]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [  0.09375162, -0.124435335,   0.06574867, -0.023810884,   0.08656448,  0.104850195,
  -0.040182292, -0.007851038, -0.016217625,    0.1361082],
 [-0.062396336,   0.08281786, -0.043758992,   0.01584732,  -0.05761294,  -0.06978299,
   0.026743302, 0.0052252538,  0.010793631,  -0.09058674]]
Loss: 0.4256891
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.18452805],
 [-0.12899026]]
∂L/∂layer1 = [[          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [   0.08978041,   -0.11994719,   0.062917426,  -0.022909585,    0.08303144,    0.10047636,
   -0.038630243, -0.0075702597,  -0.015591215,    0.13058722],
 [  -0.06275902,   0.083846435,  -0.043981038,    0.01601444,  -0.058041297,   -0.07023578,
    0.027003618,   0.005291823,   0.010898695,   -0.09128411]]
Loss: 0.42343885
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.17764306],
 [-0.12992996]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [  0.08611148, -0.115765825,  0.060307615, -0.022072455,   0.07976325,  0.096430376,
  -0.037188895, -0.007308357, -0.015009486,   0.12547429],
 [ -0.06298282,   0.08467232,  -0.04410961,   0.01614402,  -0.05833966,  -0.07053017,
    0.02720034, 0.0053454074,  0.010978092,  -0.09177318]]
Loss: 0.4213786
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.17123967],
 [-0.13059705]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [   0.0827157,  -0.11186533,  0.057897244, -0.021293767,  0.076734796,   0.09268116,
   -0.03584837, -0.007063774, -0.014468448,   0.12073135],
 [ -0.06308366,   0.08531482,  -0.04415571,   0.01623983, -0.058522295,   -0.0706839,
   0.027339993,  0.005387233,  0.011034457,  -0.09207655]]
Loss: 0.419482
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.16527513],
 [-0.13101962]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [  0.07956703, -0.108221985,  0.055666752, -0.020568334,   0.07392358,    0.0892009,
  -0.034599714, -0.006835082,  -0.01396449,  0.116324194],
 [-0.063075684,   0.08579151, -0.044129066,  0.016305268,  -0.05860192,  -0.07071281,
   0.027428458,  0.005418419,   0.01107016, -0.092214435]]
Loss: 0.41772705
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.15971094],
 [-0.13122296]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [ 0.076642305,  -0.10481434,    0.0535988, -0.019891495,   0.07130953,   0.08596473,
  -0.033434875, -0.006620981, -0.013494358,  0.112222254],
 [ -0.06297146,  0.086118385,  -0.04403827,  0.016343407, -0.058589894,  -0.07063102,
   0.027471025, 0.0054399823,  0.011087341, -0.092204936]]
Loss: 0.4160954
∂L/∂ŷ = [[     -0.25],
 [     -0.25],
 [ 0.1545125],
 [-0.1312299]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [  0.07392088,  -0.10162293,   0.05167799,  -0.01925905,   0.06887471,   0.08295049,
    -0.0323466,  -0.00642029, -0.013055129,   0.10839817],
 [-0.062782176,   0.08630996, -0.043890934,  0.016357018,  -0.05849638,  -0.07045116,
    0.02747248, 0.0054528536,  0.011087928,  -0.09206428]]
Loss: 0.41457158
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.14964825],
 [-0.13106102]]
∂L/∂layer1 = [[         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [         0.0,          0.0,          0.0,          0.0,          0.0,          0.0,
           0.0,          0.0,          0.0,          0.0],
 [  0.07138416,   -0.0986299,  0.049890514, -0.018667182,  0.066602945,   0.08013815,
  -0.031328287,  -0.00623192, -0.012644137,   0.10482717],
 [-0.062517814,   0.08637945, -0.043693807,  0.016348602,  -0.05833045,  -0.07018449,
   0.027437123,  0.005457877,  0.011073658,  -0.09180699]]
Loss: 0.41314268
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [  0.1450899],
 [-0.13073498]]
∂L/∂layer1 = [[          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [   0.06901559,   -0.09581909,   0.048224125,  -0.018112455,    0.06447981,   0.077509835,
   -0.030374015, -0.0060548857,  -0.012258992,    0.10148715],
 [  -0.06218732,    0.08633894,  -0.043452926,   0.016320443,    -0.0581003,   -0.06984116,
    0.027368868,   0.005455827,   0.011046111,  -0.091446206]]
Loss: 0.41179782
∂L/∂ŷ = [[      -0.25],
 [      -0.25],
 [ 0.14081168],
 [-0.13026857]]
∂L/∂layer1 = [[          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [          0.0,           0.0,           0.0,           0.0,           0.0,           0.0,
            0.0,           0.0,           0.0,           0.0],
 [  0.066800244,   -0.09317576,   0.046667818,  -0.017591748,    0.06249226,    0.07504944,
   -0.029478388, -0.0058882837,  -0.011897515,    0.09835809],
 [  -0.06179865,   0.086199336,   -0.04317362,   0.016274586,  -0.057813223,  -0.069430195,
    0.027271228,  0.0054474054,   0.011006703,   -0.09099364]]

Mengkompilasi ulang aktivasi selama backpropagation untuk menghemat memori (checkpointing)

Checkpointing adalah teknik tradisional dalam diferensiasi otomatis mode terbalik untuk menghemat memori. Alih-alih menyimpan nilai menengah yang besar dalam perhitungan asli untuk menghitung turunan, nilai-nilai menengah malah dihitung ulang sesuai kebutuhan selama backpropagation.

Teknik ini telah diwujudkan dalam perpustakaan pembelajaran mendalam modern juga. Dalam Swift, API withRecomputationInPullbacks(_:) memungkinkan Anda untuk mengontrol apa yang harus recompute selama backpropagation, dan tersedia pada semua Differentiable jenis.

Tetapi hari ini, mari kita belajar bagaimana mendefinisikan API titik-titik pemeriksaan gradien dari awal, hanya dalam beberapa baris kode.

API checkpoint gradien kami

Kita dapat mendefinisikan API pemeriksa gradien kita sendiri, makeRecomputedInGradient(_:) , dalam hal fungsi pustaka standar fungsi makeRecomputedInGradient(_:) differentiableFunction(from:) , yang merupakan singkatan untuk membuat fungsi terdiferensiasi langsung dari fungsi turunan (juga disebut "vektor-produk Jacobian) (VJP) berfungsi ").

Seperti yang telah kita lihat sebelumnya, fungsi turunan mengembalikan tuple dari hasil fungsi asli dan penutupan pullback. Kami mengembalikan yang original(x) pada value: pullback(at:in:) dan memanggil pullback(at:in:) pada yang original untuk mengevaluasi kembali fungsi aslinya dan mendapatkan pullback.

 /// 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) })
    }
}
 

Verifikasi itu berfungsi

 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

Perluas itu ke modul jaringan saraf

Dalam contoh ini, kami mendefinisikan jaringan saraf convolutional sederhana.

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

Kami ingin membuat aktivasi di lapisan konvolusi ( conv ) dihitung ulang selama backpropagation. Namun, menggunakan makeRecomputedInGradient(_:) dapat membuat kode yang dihasilkan terlihat rumit, terutama ketika kita ingin menerapkan lapisan secara berurutan menggunakan sequenced(in:through:_:_:_:_:) .

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

Jadi, mengapa kita tidak mendefinisikan tipe layer khusus yang membungkus layer dan membuat aktivasinya dihitung ulang selama backpropagation? Ayo lakukan.

Pertama, kita mendefinisikan fungsi makeRecomputedInGradient(_:) yang menggunakan fungsi biner.

 // 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) })
    }
}
 

Kemudian, kita mendefinisikan layer generik 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)
    }
}
 

Akhirnya, kita dapat menambahkan metode pada semua layer yang mengembalikan layer yang sama kecuali aktivasinya dibuang selama aplikasi dan dihitung ulang selama backpropagation.

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

Kembali ke model, yang harus kita ubah adalah membungkus layer konvolusi ke dalam layer aktivasi-recomputing.

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

Sekarang, cukup gunakan dalam model!

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

Ketika kita menjalankan loop pelatihan, kita dapat melihat bahwa aktivasi lapisan konvolusi dihitung dua kali: sekali selama aplikasi lapisan, dan sekali selama backpropagation.

 // 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.3176332
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 2
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.7285542
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 3
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.3323467
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 4
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 2.0400991
  Running backpropagation...
    Applying Conv2D<Float> layer...
Starting training step 5
  Running original computation...
    Applying Conv2D<Float> layer...
  Loss: 1.8097637
  Running backpropagation...
    Applying Conv2D<Float> layer...

Sama seperti itu, sangat mudah untuk mendefinisikan perpustakaan pemrograman generik terdiferensiasi untuk domain yang berbeda.