Trang này được dịch bởi Cloud Translation API.
Switch to English

Sự khác biệt tùy chỉnh

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub

Hướng dẫn này sẽ chỉ cho bạn cách xác định các dẫn xuất tùy chỉnh của riêng bạn, thực hiện phẫu thuật phái sinh và triển khai API điểm kiểm tra gradient của riêng bạn chỉ trong 5 dòng Swift.

Khai báo các dẫn xuất tùy chỉnh

Bạn có thể xác định các dẫn xuất tùy chỉnh cho bất kỳ hàm Swift nào có các tham số và kết quả khác nhau. Bằng cách đó, bạn thậm chí có thể nhập một hàm C và làm cho nó trở nên khác biệt.

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

Ngừng tuyên truyền các dẫn xuất

Thường được gọi là "dừng gradient" trong các trường hợp sử dụng máy học, phương thức withoutDerivative(at:) ngăn các dẫn xuất lan truyền.

Ngoài ra, withoutDerivative(at:) đôi khi có thể giúp trình biên dịch Swift xác định những gì không để phân biệt và tạo ra các dẫn xuất hiệu quả hơn. Khi có thể phát hiện ra rằng đạo hàm của một hàm sẽ luôn bằng 0, trình biên dịch Swift sẽ đưa ra một cảnh báo. Sử dụng rõ ràng withoutDerivative(at:) im lặng cảnh báo đó.

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

Giải phẫu phái sinh

Phương thức withDerivative(_:) thực hiện các hoạt động tùy ý (bao gồm cả đột biến) chạy trên gradient ở một giá trị trong quá trình lan truyền ngược của hàm bao quanh.

Sử dụng điều này để gỡ lỗi hoặc thực hiện các chỉnh sửa thử nghiệm đối với việc nhân giống ngược.

Nó hoạt động ở mọi nơi

Tất cả các API phân biệt được cung cấp bởi thư viện tiêu chuẩn được xác định chung cho tất cả các loại tuân theo giao thức Có thể Differentiable : vectơ Float , Double , Float80 , SIMD và thậm chí cả các loại của riêng bạn!

Đọc tài liệu kỹ thuật Các loại có thể phân biệt để hiểu thêm về giao thức Có thể 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

Sử dụng nó trong một mô-đun mạng thần kinh

Giống như cách chúng ta sử dụng nó trong một hàm Float đơn giản, chúng ta có thể sử dụng nó trong bất kỳ ứng dụng số nào, chẳng hạn như mạng nơ-ron sau đây được xây dựng bằng 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]]

Tính toán lại các kích hoạt trong quá trình nhân giống ngược để tiết kiệm bộ nhớ (điểm kiểm tra)

Checkpointing là một kỹ thuật truyền thống trong chế độ phân biệt tự động đảo ngược để tiết kiệm bộ nhớ. Thay vì lưu các giá trị trung gian lớn trong tính toán ban đầu để tính toán các dẫn xuất, các giá trị trung gian thay vì được tính toán lại khi cần trong quá trình nhân giống ngược.

Kỹ thuật này cũng đã được thực hiện trong các thư viện học sâu hiện đại. Trong Swift, API withRecomputationInPullbacks(_:) cho phép bạn kiểm soát những gì để recompute trong lan truyền ngược, và nó có sẵn trên tất cả các Differentiable loại.

Nhưng hôm nay, chúng ta hãy tìm hiểu cách xác định API điểm kiểm tra độ dốc của riêng mình từ đầu, chỉ trong một vài dòng mã.

API điểm kiểm tra độ dốc của chúng tôi

Chúng ta có thể xác định API điểm kiểm tra độ dốc của riêng mình, makeRecomputedInGradient(_:) , về mặt chức năng thư viện tiêu chuẩn makeRecomputedInGradient(_:) differentiableFunction(from:) , là cách viết tắt để tạo một hàm có thể phân biệt trực tiếp từ một hàm phái sinh (còn được gọi là "sản phẩm vector-Jacobian (VJP) hàm ").

Như chúng ta đã thấy trước đây, hàm đạo hàm trả về một bộ giá trị của kết quả của hàm ban đầu và một bao đóng pullback. Chúng tôi trả về value: original(x) trong value: pullback(at:in:) và gọi pullback(at:in:) trên original để đánh giá lại hàm ban đầu và nhận được một 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) })
    }
}

Xác minh nó hoạt động

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

Mở rộng nó sang các mô-đun mạng thần kinh

Trong ví dụ này, chúng tôi xác định một mạng nơ-ron tích chập đơn giản.

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

Chúng tôi muốn thực hiện các kích hoạt trong lớp tích chập ( conv ) được tính toán lại trong quá trình nhân giống ngược. Tuy nhiên, việc sử dụng makeRecomputedInGradient(_:) có thể làm cho mã kết quả trông cồng kềnh, đặc biệt khi chúng ta muốn áp dụng các lớp một cách tuần tự bằng cách sử dụng tuần sequenced(in:through:_:_:_:_:) .

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

Vì vậy, tại sao chúng ta không xác định một loại lớp đặc biệt bao bọc một lớp và làm cho các kích hoạt của nó được tính toán lại trong quá trình nhân giống ngược? Hãy làm nó.

Đầu tiên, chúng ta định nghĩa một makeRecomputedInGradient(_:) nhận một hàm nhị phân.

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

Sau đó, chúng tôi xác định một lớp chung chung 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)
    }
}

Cuối cùng, chúng ta có thể thêm một phương thức trên tất cả các lớp trả về cùng một lớp ngoại trừ các kích hoạt của nó bị loại bỏ trong quá trình ứng dụng và được tính toán lại trong quá trình nhân giống ngược.

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

Quay lại mô hình, tất cả những gì chúng ta phải thay đổi là quấn lớp chập vào lớp tính toán lại kích hoạt.

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

Bây giờ, chỉ cần sử dụng nó trong mô hình!

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

Khi chúng ta chạy một vòng lặp huấn luyện, chúng ta có thể thấy rằng các kích hoạt của lớp tích chập được tính hai lần: một lần trong khi áp dụng lớp và một lần trong quá trình nhân giống ngược.

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

Giống như vậy, thật dễ dàng để xác định các thư viện lập trình có thể phân biệt chung cho các miền khác nhau.