-
Notifications
You must be signed in to change notification settings - Fork 137
Added support for composing layers. #326
Conversation
|
This is very cool stuff :o |
|
@eaplatanios I really like the idea of composable layers for describing these little mini architectures, but I find the operator Overall, I look forward to having this in the API! |
|
Combinators and operators like For now, I'd strongly suggest going a different route: Define a Note: This is not yet possible today because the differentiation feature does not yet support differentiating closure contexts, and other ways of defining it may be limited to the fact that Swift doesn't yet support parameterized extensions. Another (much nicer) possibility is to sugar away combinator types with function builders: public struct Sequential<Layer1: Layer, Layer2: Layer>: Layer where Layer1.Output == Layer2.Input {
internal var layer1: Layer1
internal var layer2: Layer2
public typealias Input = Layer1.Input
public typealias Output = Layer2.Output
internal init(_ layer1: Layer1, _ layer2: Layer2) {
self.layer1 = layer1
self.layer2 = layer2
}
@differentiable
public func callAsFunction(_ input: Input) -> Output {
layer2(layer1(input))
}
public init(@LayerBuilder layers: () -> Self) {
self = layers()
}
}
@_functionBuilder
public struct LayerBuilder {
public static func buildBlock<L1: Layer, L2: Layer>(_ l1: L1, _ l2: L2) -> Sequential<L1, L2>
where L1.Output == L2.Input {
Sequential(l1, l2)
}
public static func buildBlock<L1: Layer, L2: Layer, L3: Layer>(_ l1: L1, _ l2: L2, _ l3: L3) -> Sequential<L1, Sequential<L2, L3>>
where L1.Output == L2.Input, L2.Output == L3.Input {
Sequential(l1, Sequential(l2, l3))
}
public static func buildBlock<L1: Layer, L2: Layer, L3: Layer, L4: Layer>(_ l1: L1, _ l2: L2, _ l3: L3, _ l4: L4) -> Sequential<L1, Sequential<L2, Sequential<L3, L4>>>
where L1.Output == L2.Input, L2.Output == L3.Input {
Sequential(l1, Sequential(l2, Sequential(l3, l4)))
}
}
let layers = Sequential {
Dense<Float>(inputSize: 3, outputSize: 3)
Conv2D<Float>(filterShape: (1, 1, 1))
}This unfortunately won't compile today because type inference isn't so flexible with function builders, and I'm getting quite a few compiler crashers because the feature is very much experimental. |
|
@rxwei I like the function builder approach you proposed. I implemented it and it compiles fine for me. Could you please check the changes I just pushed? Also, I'm not sure what to do to make the generated file conform to our style guidelines without adding a bunch of heuristics to the gyb file. Should I just remove the gyb file and reformat the generated file manually? |
|
That’s awesome! Yeah it somehow didn’t compile for me when I wrote the example, but I’m glad it worked. |
rxwei
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine to leave the gyb in. Don't worry about formatting for generated files.
|
@eaplatanios, a small doubt here. why do we have several functions here and in the layer.swift file explicitly mentioning the number of layers ? Like layer1, layer2... A very weird question but can't recursive functions or loops work in this case? |
|
This is because Swift does not support variadic generic parameters. When that feature becomes possible, only one builder function needs to be defined. |
@rxwei not sure if we want to merge this following our earlier discussion but I open this to gather feedback and consider it.
Quoting the reason I brought this up from our earlier discussion: