Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Niestandardowe zróżnicowanie

Zobacz na TensorFlow.org Wyświetl źródło na GitHub

W tym samouczku dowiesz się, jak zdefiniować własne niestandardowe pochodne, wykonać operacje pochodne i wdrożyć własny interfejs API z gradientowymi punktami kontrolnymi w zaledwie 5 liniach języka Swift.

Deklarowanie niestandardowych instrumentów pochodnych

Możesz zdefiniować niestandardowe pochodne dla dowolnej funkcji Swift, która ma różne parametry i wyniki. W ten sposób możesz nawet zaimportować funkcję C i uczynić ją różnicowalną.

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

Zatrzymaj propagację pochodnych

Powszechnie znana jako „stop gradient” w przypadkach użycia uczenia maszynowego, metoda withoutDerivative(at:) zatrzymuje propagację pochodnych.

Plus, withoutDerivative(at:) może czasami pomóc kompilatorowi Swift w zidentyfikowaniu tego, czego nie należy różnicować, i tworzeniu wydajniejszych pochodnych. Gdy zostanie wykryte, że pochodna funkcji zawsze będzie wynosić zero, kompilator Swift wyświetli ostrzeżenie. Jawne użycie withoutDerivative(at:) ucisza to ostrzeżenie.

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

Chirurgia pochodna

Metoda withDerivative(_:) sprawia, że ​​dowolne operacje (w tym mutacje) są wykonywane na gradiencie przy wartości podczas wstecznej propagacji funkcji obejmującej.

Użyj tego do debugowania lub eksperymentalnych poprawek do wstecznej propagacji.

Działa wszędzie

Wszystkie API różnicowania dostarczane przez bibliotekę standardową są zdefiniowane ogólnie dla wszystkich typów zgodnych z protokołem Differentiable : Float , Double , Float80 , wektorów SIMD, a nawet własnych typów!

Przeczytaj dokument techniczny Differentiable Types, aby uzyskać więcej informacji na temat protokołu 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

Użyj go w module sieci neuronowej

Tak jak użyliśmy go w prostej funkcji Float , możemy go użyć w dowolnej aplikacji numerycznej, takiej jak poniższa sieć neuronowa zbudowana przy użyciu biblioteki 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]]

Ponowne obliczanie aktywacji podczas wstecznej propagacji w celu zaoszczędzenia pamięci (punkty kontrolne)

Punktowanie kontrolne jest tradycyjną techniką automatycznego różnicowania w trybie odwrotnym do oszczędzania pamięci. Zamiast zapisywać duże wartości pośrednie w pierwotnym obliczeniu do obliczania pochodnych, wartości pośrednie są zamiast tego ponownie obliczane w razie potrzeby podczas propagacji wstecznej.

Ta technika została zrealizowana również w nowoczesnych bibliotekach uczenia głębokiego. W Swift API withRecomputationInPullbacks(_:) umożliwia kontrolowanie tego, co ma być ponownie obliczane podczas propagacji wstecznej i jest dostępne dla wszystkich typów Differentiable .

Ale dzisiaj nauczmy się, jak zdefiniować od podstaw nasze własne interfejsy API z gradientowymi punktami kontrolnymi, w zaledwie kilku wierszach kodu.

Nasz gradientowy interfejs API z punktami kontrolnymi

Możemy zdefiniować własne gradientowe API, makeRecomputedInGradient(_:) , w kategoriach standardowej funkcji bibliotecznej differentiableFunction(from:) , która jest skrótem do tworzenia funkcji różniczkowalnej bezpośrednio z funkcji pochodnej (zwanej także "wektorowo-jakobianem iloczynem (VJP) ”).

Jak widzieliśmy wcześniej, funkcja pochodna zwraca krotkę wyniku pierwotnej funkcji i zamknięcie typu pullback. Zwracamy original(x) w value: i wywołujemy pullback(at:in:) na original aby ponownie ocenić oryginalną funkcję i uzyskać wycofanie.

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

Sprawdź, czy działa

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

Rozszerz go na moduły sieci neuronowej

W tym przykładzie definiujemy prostą konwolucyjną sieć neuronową.

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

Chcemy, aby aktywacje w warstwie splotu ( conv ) były ponownie obliczane podczas propagacji wstecznej. Jednak użycie makeRecomputedInGradient(_:) może sprawić, że wynikowy kod będzie wyglądał nieporęcznie, zwłaszcza gdy chcemy nakładać warstwy sekwencyjnie, używając sequenced(in:through:_:_:_:_:) .

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

Dlaczego więc nie zdefiniujemy specjalnego typu warstwy, która otacza warstwę i powoduje, że jej aktywacje są ponownie obliczane podczas propagacji wstecznej? Zróbmy to.

Najpierw definiujemy funkcję makeRecomputedInGradient(_:) która przyjmuje funkcję binarną.

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

Następnie definiujemy ogólną warstwę 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)
    }
}

Na koniec możemy dodać metodę do wszystkich warstw, która zwraca tę samą warstwę, z wyjątkiem tego, że jej aktywacje są odrzucane podczas aplikacji i ponownie obliczane podczas wstecznej propagacji.

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

W modelu wszystko, co musimy zmienić, to owinąć warstwę splotu w warstwę aktywacyjno-przeliczającą.

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

Teraz po prostu użyj go w modelu!

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

Kiedy uruchamiamy pętlę treningową, widzimy, że aktywacje warstwy splotu są obliczane dwukrotnie: raz podczas aplikacji warstwy i raz podczas wstecznej propagacji.

// 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...

W ten sposób bardzo łatwo jest zdefiniować ogólne, zróżnicowane biblioteki programistyczne dla różnych domen.