این صفحه به‌وسیله ‏Cloud Translation API‏ ترجمه شده است.
Switch to English

تمایز سفارشی

مشاهده در TensorFlow.org در Google Colab اجرا کنید مشاهده منبع در GitHub

این آموزش به شما نشان می دهد که چگونه مشتقات سفارشی خود را تعریف کنید ، جراحی مشتقات را انجام دهید و API گریدانت خود را فقط در 5 خط Swift پیاده سازی کنید.

اعلام مشتقات سفارشی

برای هر تابع 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:) گاهی اوقات می تواند به کامپایلر Swift کمک کند تا تشخیص دهد که چه چیزی را نباید از هم تفکیک کرد و مشتقات کارآمدتر را تولید کرد. وقتی قابل تشخیص است که مشتق یک تابع همیشه صفر است ، کامپایلر Swift یک هشدار ایجاد می کند. به صراحت با استفاده از بدون withoutDerivative(at:) این هشدار را 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

جراحی مشتق شده

متد withDerivative(_:) باعث می شود عملیات دلخواه (از جمله جهش) بر روی شیب با مقداری در طی انتشار مجدد تابع محصور اجرا شود.

برای رفع اشکال یا ایجاد اصلاحات آزمایشی در تبلیغ عقاید ، از این موارد استفاده کنید.

هرجایی کار میکنه

تمام 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]]

محاسبه مجدد فعال سازی ها در هنگام انتشار مجدد برای صرفه جویی در حافظه (ایست بازرسی)

Checkpointing یک روش سنتی در تمایز خودکار در حالت معکوس برای صرفه جویی در حافظه است. به جای صرفه جویی در مقادیر متوسط ​​بزرگ در محاسبات اصلی برای مشتقات محاسباتی ، مقادیر متوسط ​​در عوض در صورت نیاز در هنگام تولید مجدد محاسبه می شوند.

این روش در کتابخانه های مدرن یادگیری عمیق نیز محقق شده است. در Swift ، API withRecomputationInPullbacks(_:) شما را قادر می سازد کنترل کنید که چه عواملی را در هنگام انتشار مجدد محاسبه کنید ، و در همه انواع Differentiable قابل دسترس است.

اما امروز ، بیایید یاد بگیریم که چگونه فقط با چند خط کد ، API های بازرسی گرادیان خود را از ابتدا تعریف کنیم.

رابط برنامه نویسی gradient ما

ما می توانیم API خود را از نظر گرادیان ، makeRecomputedInGradient(_:) ، از نظر عملکرد استاندارد کتابخانه differentiableFunction(from:) تعریف کنیم ، که makeRecomputedInGradient(_:) برای ایجاد یک عملکرد makeRecomputedInGradient(_:) است که مستقیماً از یک تابع مشتق شده است (همچنین "بردار-محصولات ژاکوبین" نامیده می شود (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(_:) می تواند کد نتیجه را دست و پا گیر به نظر 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...

دقیقاً به همین ترتیب ، تعریف کتابخانه های برنامه نویسی متمایز عمومی برای دامنه های مختلف بسیار آسان است.