Skip to content
This repository was archived by the owner on Jul 1, 2023. It is now read-only.

Commit 7c96679

Browse files
committed
Add @_Freezable property wrapper.
`@_Freezable` wraps differentiable values and provides toggleable "trainability" via the `isFrozen` property and `freeze`/`unfreeze` mutating methods. When `isFrozen` is true, accesses to `value` have a derivative of zero. The alternative name `@_Trainable` was considered, but `@_Trainable` gives the impression that "only properties marked with `@_Trainable` are trainable", which is not true: all properties are trainable (i.e. their derivatives are propagated) by default.
1 parent 1aceb2d commit 7c96679

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

Sources/TensorFlow/Freezable.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// A wrapper around a differentiable value with "freezable" derivatives.
16+
///
17+
/// When `isFrozen` is true, accesses to `wrappedValue` have a derivative of zero.
18+
@propertyWrapper
19+
public struct _Freezable<Value: Differentiable> {
20+
@noDerivative public var isFrozen: Bool = false
21+
private var _value: Value
22+
23+
public init(wrappedValue: Value) {
24+
_value = wrappedValue
25+
}
26+
27+
public var projectedValue: Self {
28+
get { return self }
29+
set { self = newValue }
30+
}
31+
32+
/// The wrapped differentiable value.
33+
@differentiable(vjp: _vjpValue)
34+
public var wrappedValue: Value {
35+
get { _value }
36+
set { _value = newValue }
37+
}
38+
39+
@usableFromInline
40+
func _vjpValue() -> (value: Value, pullback: (Value.TangentVector) -> TangentVector) {
41+
return (_value, { [isFrozen = self.isFrozen] v in
42+
isFrozen ? .zero : v
43+
})
44+
}
45+
}
46+
47+
extension _Freezable {
48+
/// Freeze derivatives for `wrappedValue`. Accesses to `wrappedValue` will always have a
49+
/// derivative of zero.
50+
public mutating func freeze() {
51+
isFrozen = true
52+
}
53+
54+
/// Unfreeze derivatives for `wrappedValue`.
55+
public mutating func unfreeze() {
56+
isFrozen = false
57+
}
58+
}
59+
60+
extension _Freezable: Differentiable {
61+
public typealias TangentVector = Value.TangentVector
62+
public mutating func move(along direction: TangentVector) {
63+
_value.move(along: direction)
64+
}
65+
}
66+
67+
extension _Freezable: EuclideanDifferentiable where Value: EuclideanDifferentiable {
68+
public var differentiableVectorView: TangentVector {
69+
return _value.differentiableVectorView
70+
}
71+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import XCTest
16+
@testable import TensorFlow
17+
18+
final class FreezableTests: XCTestCase {
19+
func testFreezableParameters() {
20+
// A dense layer with freezable properties.
21+
struct FreezableDense : Layer {
22+
@_Freezable var weight: Tensor<Float>
23+
@_Freezable var bias: Tensor<Float>
24+
25+
init(weight: Tensor<Float>, bias: Tensor<Float>) {
26+
// Require scalar weight and bias for simplicity.
27+
precondition(weight.isScalar)
28+
precondition(bias.isScalar)
29+
self.weight = weight
30+
self.bias = bias
31+
}
32+
33+
@differentiable
34+
func callAsFunction(_ input: Tensor<Float>) -> Tensor<Float> {
35+
return input * weight + bias
36+
}
37+
}
38+
39+
var dense = FreezableDense(weight: Tensor(2), bias: Tensor(3))
40+
let x = Tensor<Float>(4)
41+
do {
42+
let (value, gradient) = valueWithGradient(at: dense, x) { dense, x in dense(x) }
43+
XCTAssertEqual(Tensor(11), value)
44+
// The gradient of `dense.weight` should be non-zero.
45+
XCTAssertEqual(FreezableDense.TangentVector(_weight: Tensor(4), _bias: Tensor(1)),
46+
gradient.0)
47+
XCTAssertEqual(Tensor(2), gradient.1)
48+
}
49+
50+
// Freeze derivatives for `dense.weight`.
51+
dense.$weight.freeze()
52+
do {
53+
let (value, gradient) = valueWithGradient(at: dense, x) { dense, x in dense(x) }
54+
// The gradient of `dense.weight` should now be zero.
55+
XCTAssertEqual(Tensor(11), value)
56+
XCTAssertEqual(FreezableDense.TangentVector(_weight: Tensor(0), _bias: Tensor(1)),
57+
gradient.0)
58+
XCTAssertEqual(Tensor(2), gradient.1)
59+
}
60+
61+
// Unfreeze derivatives for `dense.weight`.
62+
dense.$weight.unfreeze()
63+
do {
64+
let (value, gradient) = valueWithGradient(at: dense, x) { dense, x in dense(x) }
65+
XCTAssertEqual(Tensor(11), value)
66+
// The gradient of `dense.weight` should now be non-zero.
67+
XCTAssertEqual(FreezableDense.TangentVector(_weight: Tensor(4), _bias: Tensor(1)),
68+
gradient.0)
69+
XCTAssertEqual(Tensor(2), gradient.1)
70+
}
71+
}
72+
73+
static var allTests = [
74+
("testFreezableParameters", testFreezableParameters),
75+
]
76+
}

0 commit comments

Comments
 (0)