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

Commit b1db1f8

Browse files
Shashi456eaplatanios
authored andcommitted
Add Separable Conv2D Layer. (#362)
1 parent cc36830 commit b1db1f8

File tree

2 files changed

+129
-2
lines changed

2 files changed

+129
-2
lines changed

Sources/TensorFlow/Layers/Convolutional.swift

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public struct Conv2D<Scalar: TensorFlowFloatingPoint>: Layer {
138138
@noDerivative public let padding: Padding
139139
/// The dilation factor for spatial dimensions.
140140
@noDerivative public let dilations: (Int, Int)
141-
141+
142142
/// The element-wise activation function type.
143143
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
144144

@@ -470,7 +470,7 @@ public struct DepthwiseConv2D<Scalar: TensorFlowFloatingPoint>: Layer {
470470
/// The element-wise activation function type.
471471
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
472472

473-
/// Creates a `DepthwiseConv2D` layer with the specified filter, bias, activation function,
473+
/// Creates a `DepthwiseConv2D` layer with the specified filter, bias, activation function,
474474
/// strides, and padding.
475475
///
476476
/// - Parameters:
@@ -631,3 +631,111 @@ public struct ZeroPadding3D<Scalar: TensorFlowFloatingPoint>: ParameterlessLayer
631631
return input.padded(forSizes: [padding.0, padding.1, padding.2])
632632
}
633633
}
634+
635+
/// A 2-D Separable convolution layer.
636+
///
637+
/// This layer performs a depthwise convolution that acts separately on channels followed by
638+
/// a pointwise convolution that mixes channels.
639+
@frozen
640+
public struct SeparableConv2D<Scalar: TensorFlowFloatingPoint>: Layer {
641+
/// The 4-D depthwise convolution kernel.
642+
public var depthwiseFilter: Tensor<Scalar>
643+
/// The 4-D pointwise convolution kernel.
644+
public var pointwiseFilter: Tensor<Scalar>
645+
/// The bias vector.
646+
public var bias: Tensor<Scalar>
647+
/// The element-wise activation function.
648+
@noDerivative public let activation: Activation
649+
/// The strides of the sliding window for spatial dimensions.
650+
@noDerivative public let strides: (Int, Int)
651+
/// The padding algorithm for convolution.
652+
@noDerivative public let padding: Padding
653+
654+
/// The element-wise activation function type.
655+
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
656+
657+
/// Creates a `SeparableConv2D` layer with the specified depthwise and pointwise filter,
658+
/// bias, activation function, strides, and padding.
659+
///
660+
/// - Parameters:
661+
/// - depthwiseFilter: The 4-D depthwise convolution kernel
662+
/// `[filter height, filter width, input channels count, channel multiplier]`.
663+
/// - pointwiseFilter: The 4-D pointwise convolution kernel
664+
/// `[1, 1, channel multiplier * input channels count, output channels count]`.
665+
/// - bias: The bias vector.
666+
/// - activation: The element-wise activation function.
667+
/// - strides: The strides of the sliding window for spatial dimensions.
668+
/// - padding: The padding algorithm for convolution.
669+
public init(
670+
depthwiseFilter: Tensor<Scalar>,
671+
pointwiseFilter: Tensor<Scalar>,
672+
bias: Tensor<Scalar>,
673+
activation: @escaping Activation = identity,
674+
strides: (Int, Int) = (1, 1),
675+
padding: Padding = .valid
676+
) {
677+
self.depthwiseFilter = depthwiseFilter
678+
self.pointwiseFilter = pointwiseFilter
679+
self.bias = bias
680+
self.activation = activation
681+
self.strides = strides
682+
self.padding = padding
683+
}
684+
685+
/// Returns the output obtained from applying the layer to the given input.
686+
///
687+
/// - Parameter input: The input to the layer.
688+
/// - Returns: The output.
689+
@differentiable
690+
public func callAsFunction(_ input: Tensor<Scalar>) -> Tensor<Scalar> {
691+
let depthwise = depthwiseConv2D(
692+
input,
693+
filter: depthwiseFilter,
694+
strides: (1, strides.0, strides.1, 1),
695+
padding: padding)
696+
return activation(conv2D(
697+
depthwise,
698+
filter: pointwiseFilter,
699+
strides: (1, 1, 1, 1),
700+
padding: padding,
701+
dilations: (1, 1, 1, 1)) + bias)
702+
}
703+
}
704+
705+
public extension SeparableConv2D {
706+
/// Creates a `SeparableConv2D` layer with the specified depthwise and pointwise filter shape,
707+
/// strides, padding, and element-wise activation function.
708+
///
709+
/// - Parameters:
710+
/// - depthwiseFilterShape: The shape of the 4-D depthwise convolution kernel.
711+
/// - pointwiseFilterShape: The shape of the 4-D pointwise convolution kernel.
712+
/// - strides: The strides of the sliding window for spatial/spatio-temporal dimensions.
713+
/// - padding: The padding algorithm for convolution.
714+
/// - activation: The element-wise activation function.
715+
/// - filterInitializer: Initializer to use for the filter parameters.
716+
/// - biasInitializer: Initializer to use for the bias parameters.
717+
init(
718+
depthwiseFilterShape: (Int, Int, Int, Int),
719+
pointwiseFilterShape: (Int, Int, Int, Int),
720+
strides: (Int, Int) = (1, 1),
721+
padding: Padding = .valid,
722+
activation: @escaping Activation = identity,
723+
depthwiseFilterInitializer: ParameterInitializer<Scalar> = glorotUniform(),
724+
pointwiseFilterInitializer: ParameterInitializer<Scalar> = glorotUniform(),
725+
biasInitializer: ParameterInitializer<Scalar> = zeros()
726+
) {
727+
let depthwiseFilterTensorShape = TensorShape([
728+
depthwiseFilterShape.0, depthwiseFilterShape.1, depthwiseFilterShape.2,
729+
depthwiseFilterShape.3])
730+
let pointwiseFilterTensorShape = TensorShape([
731+
pointwiseFilterShape.0, pointwiseFilterShape.1, pointwiseFilterShape.2,
732+
pointwiseFilterShape.3])
733+
self.init(
734+
depthwiseFilter: depthwiseFilterInitializer(depthwiseFilterTensorShape),
735+
pointwiseFilter: pointwiseFilterInitializer(pointwiseFilterTensorShape),
736+
bias: biasInitializer([pointwiseFilterShape.3]),
737+
activation: activation,
738+
strides: strides,
739+
padding: padding)
740+
}
741+
}

Tests/TensorFlowTests/LayerTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,24 @@ final class LayerTests: XCTestCase {
164164
XCTAssertEqual(output, expected)
165165
}
166166

167+
func testSeparableConv2D() {
168+
let depthwiseFilter = Tensor(shape: [2, 2, 2, 2], scalars: (0..<16).map(Float.init))
169+
let pointwiseFilter = Tensor(shape: [1, 1, 4, 1], scalars: (0..<4).map(Float.init))
170+
let bias = Tensor<Float>([4])
171+
let layer = SeparableConv2D<Float>(depthwiseFilter: depthwiseFilter,
172+
pointwiseFilter: pointwiseFilter,
173+
bias: bias,
174+
activation: identity,
175+
strides: (2, 2),
176+
padding: .valid)
177+
let input = Tensor(shape: [2, 2, 2, 2], scalars: (0..<16).map(Float.init))
178+
let output = layer.inferring(from: input)
179+
let expected = Tensor<Float>(shape: [2, 1, 1, 1],
180+
scalars: [1016, 2616])
181+
XCTAssertEqual(output, expected)
182+
}
183+
184+
167185
func testZeroPadding1D() {
168186
let input = Tensor<Float>([0.0, 1.0, 2.0])
169187
let layer = ZeroPadding1D<Float>(padding: 2)
@@ -538,6 +556,7 @@ final class LayerTests: XCTestCase {
538556
("testConv2DDilation", testConv2DDilation),
539557
("testConv3D", testConv3D),
540558
("testDepthConv2D", testDepthConv2D),
559+
("testSeparableConv2D", testSeparableConv2D),
541560
("testZeroPadding1D", testZeroPadding1D),
542561
("testZeroPadding2D", testZeroPadding2D),
543562
("testZeroPadding3D", testZeroPadding3D),

0 commit comments

Comments
 (0)