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

Commit d87fab3

Browse files
Dave Fernandesrxwei
authored andcommitted
Add–1d–layers (#57)
* Added Conv1d, MaxPool1D and AvgPool1D layers Co-Authored-By: dave-fernandes <[email protected]>
1 parent f03a86a commit d87fab3

File tree

3 files changed

+272
-24
lines changed

3 files changed

+272
-24
lines changed

Sources/DeepLearning/Layer.swift

Lines changed: 223 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,127 @@ public extension Dense {
333333
}
334334
}
335335

336+
/// A 1-D convolution layer (e.g. temporal convolution over a time-series).
337+
///
338+
/// This layer creates a convolution filter that is convolved with the layer input to produce a
339+
/// tensor of outputs.
340+
@_fixed_layout
341+
public struct Conv1D<Scalar: TensorFlowFloatingPoint>: Layer {
342+
/// The 3-D convolution kernel `[width, inputChannels, outputChannels]`.
343+
public var filter: Tensor<Scalar>
344+
/// The bias vector `[outputChannels]`.
345+
public var bias: Tensor<Scalar>
346+
/// An activation function.
347+
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
348+
/// The element-wise activation function.
349+
@noDerivative public let activation: Activation
350+
/// The stride of the sliding window for temporal dimension.
351+
@noDerivative public let stride: Int32
352+
/// The padding algorithm for convolution.
353+
@noDerivative public let padding: Padding
354+
355+
/// Creates a `Conv1D` layer with the specified filter, bias, activation function, stride, and
356+
/// padding.
357+
///
358+
/// - Parameters:
359+
/// - filter: The 3-D convolution kernel `[width, inputChannels, outputChannels]`.
360+
/// - bias: The bias vector `[outputChannels]`.
361+
/// - activation: The element-wise activation function.
362+
/// - stride: The stride of the sliding window for temporal dimension.
363+
/// - padding: The padding algorithm for convolution.
364+
public init(
365+
filter: Tensor<Scalar>,
366+
bias: Tensor<Scalar>,
367+
activation: @escaping Activation,
368+
stride: Int,
369+
padding: Padding
370+
) {
371+
self.filter = filter
372+
self.bias = bias
373+
self.activation = activation
374+
self.stride = Int32(stride)
375+
self.padding = padding
376+
}
377+
378+
/// Returns the output obtained from applying the layer to the given input.
379+
///
380+
/// - Parameters:
381+
/// - input: The input to the layer `[batchCount, width, inputChannels]`.
382+
/// - context: The contextual information for the layer application, e.g. the current learning
383+
/// phase.
384+
/// - Returns: The output `[batchCount, newWidth, outputChannels]`.
385+
@differentiable
386+
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
387+
let conv2D = input.expandingShape(at: 1).convolved2D(
388+
withFilter: filter.expandingShape(at: 0), strides: (1, 1, stride, 1), padding: padding)
389+
return activation(conv2D.squeezingShape(at: 1) + bias)
390+
}
391+
}
392+
393+
public extension Conv1D where Scalar.RawSignificand: FixedWidthInteger {
394+
/// Creates a `Conv1D` layer with the specified filter shape, stride, padding, and
395+
/// element-wise activation function. The filter tensor is initialized using Glorot uniform
396+
/// initialization with the specified generator. The bias vector is initialized with zeros.
397+
///
398+
/// - Parameters:
399+
/// - filterShape: The 3-D shape of the filter, representing
400+
/// `[width, inputChannels, outputChannels]`.
401+
/// - stride: The stride of the sliding window for temporal dimension.
402+
/// - padding: The padding algorithm for convolution.
403+
/// - activation: The element-wise activation function.
404+
/// - generator: The random number generator for initialization.
405+
///
406+
/// - Note: Use `init(filterShape:stride:padding:activation:seed:)` for faster random
407+
/// initialization.
408+
init<G: RandomNumberGenerator>(
409+
filterShape: (Int, Int, Int),
410+
stride: Int = 1,
411+
padding: Padding = .valid,
412+
activation: @escaping Activation = identity,
413+
generator: inout G
414+
) {
415+
let filterTensorShape = TensorShape([
416+
Int32(filterShape.0), Int32(filterShape.1), Int32(filterShape.2)])
417+
self.init(
418+
filter: Tensor(glorotUniform: filterTensorShape),
419+
bias: Tensor(zeros: TensorShape([Int32(filterShape.2)])),
420+
activation: activation,
421+
stride: stride,
422+
padding: padding)
423+
}
424+
}
425+
426+
public extension Conv1D {
427+
/// Creates a `Conv1D` layer with the specified filter shape, strides, padding, and
428+
/// element-wise activation function. The filter tensor is initialized using Glorot uniform
429+
/// initialization with the specified seed. The bias vector is initialized with zeros.
430+
///
431+
/// - Parameters:
432+
/// - filterShape: The 3-D shape of the filter, representing
433+
/// `[width, inputChannels, outputChannels]`.
434+
/// - stride: The stride of the sliding window for temporal dimension.
435+
/// - padding: The padding algorithm for convolution.
436+
/// - activation: The element-wise activation function.
437+
/// - seed: The random seed for initialization. The default value is random.
438+
init(
439+
filterShape: (Int, Int, Int),
440+
stride: Int = 1,
441+
padding: Padding = .valid,
442+
activation: @escaping Activation = identity,
443+
seed: (Int64, Int64) = (Int64.random(in: Int64.min..<Int64.max),
444+
Int64.random(in: Int64.min..<Int64.max))
445+
) {
446+
let filterTensorShape = TensorShape([
447+
Int32(filterShape.0), Int32(filterShape.1), Int32(filterShape.2)])
448+
self.init(
449+
filter: Tensor(glorotUniform: filterTensorShape, seed: seed),
450+
bias: Tensor(zeros: TensorShape([Int32(filterShape.2)])),
451+
activation: activation,
452+
stride: Int32(stride),
453+
padding: padding)
454+
}
455+
}
456+
336457
/// A 2-D convolution layer (e.g. spatial convolution over images).
337458
///
338459
/// This layer creates a convolution filter that is convolved with the layer input to produce a
@@ -347,8 +468,7 @@ public struct Conv2D<Scalar: TensorFlowFloatingPoint>: Layer {
347468
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
348469
/// The element-wise activation function.
349470
@noDerivative public let activation: Activation
350-
/// The strides of the sliding window for each dimension of a 4-D input.
351-
/// Strides in non-spatial dimensions must be `1`.
471+
/// The strides of the sliding window for spatial dimensions.
352472
@noDerivative public let strides: (Int32, Int32)
353473
/// The padding algorithm for convolution.
354474
@noDerivative public let padding: Padding
@@ -357,11 +477,11 @@ public struct Conv2D<Scalar: TensorFlowFloatingPoint>: Layer {
357477
/// padding.
358478
///
359479
/// - Parameters:
360-
/// - filter: The filter.
361-
/// - bias: The bias.
362-
/// - activation: The activation activation.
363-
/// - strides: The strides.
364-
/// - padding: The padding.
480+
/// - filter: The 4-D convolution kernel.
481+
/// - bias: The bias vector.
482+
/// - activation: The element-wise activation function.
483+
/// - strides: The strides of the sliding window for spatial dimensions.
484+
/// - padding: The padding algorithm for convolution.
365485
public init(
366486
filter: Tensor<Scalar>,
367487
bias: Tensor<Scalar>,
@@ -397,10 +517,10 @@ public extension Conv2D {
397517
/// initialization with the specified generator. The bias vector is initialized with zeros.
398518
///
399519
/// - Parameters:
400-
/// - filterShape: The shape of the filter, represented by a tuple of `4` integers.
401-
/// - strides: The strides.
402-
/// - padding: The padding.
403-
/// - activation: The activation function.
520+
/// - filterShape: The shape of the 4-D convolution kernel.
521+
/// - strides: The strides of the sliding window for spatial dimensions.
522+
/// - padding: The padding algorithm for convolution.
523+
/// - activation: The element-wise activation function.
404524
/// - generator: The random number generator for initialization.
405525
///
406526
/// - Note: Use `init(filterShape:strides:padding:activation:seed:)` for faster random
@@ -430,14 +550,11 @@ public extension Conv2D {
430550
/// initialization with the specified seed. The bias vector is initialized with zeros.
431551
///
432552
/// - Parameters:
433-
/// - filterShape: The shape of the filter, represented by a tuple of `4` integers.
434-
/// - strides: The strides.
435-
/// - padding: The padding.
436-
/// - activation: The activation function.
553+
/// - filterShape: The shape of the 4-D convolution kernel.
554+
/// - strides: The strides of the sliding window for spatial dimensions.
555+
/// - padding: The padding algorithm for convolution.
556+
/// - activation: The element-wise activation function.
437557
/// - seed: The random seed for initialization. The default value is random.
438-
///
439-
/// - Note: Use `init(filterShape:strides:padding:activation:seed:)` for faster random
440-
/// initialization.
441558
init(
442559
filterShape: (Int, Int, Int, Int),
443560
strides: (Int, Int) = (1, 1),
@@ -450,11 +567,11 @@ public extension Conv2D {
450567
Int32(filterShape.0), Int32(filterShape.1),
451568
Int32(filterShape.2), Int32(filterShape.3)])
452569
self.init(
453-
filter: Tensor(glorotUniform: filterTensorShape, seed: seed),
454-
bias: Tensor(zeros: TensorShape([Int32(filterShape.3)])),
455-
activation: activation,
456-
strides: (Int32(strides.0), Int32(strides.1)),
457-
padding: padding)
570+
filter: Tensor(glorotUniform: filterTensorShape, seed: seed),
571+
bias: Tensor(zeros: TensorShape([Int32(filterShape.3)])),
572+
activation: activation,
573+
strides: (Int32(strides.0), Int32(strides.1)),
574+
padding: padding)
458575
}
459576
}
460577

@@ -583,6 +700,47 @@ public struct BatchNorm<Scalar: TensorFlowFloatingPoint>: Layer {
583700
}
584701
}
585702

703+
/// A max pooling layer for temporal data.
704+
@_fixed_layout
705+
public struct MaxPool1D<Scalar: TensorFlowFloatingPoint>: Layer {
706+
/// The size of the sliding reduction window for pooling.
707+
@noDerivative let poolSize: Int32
708+
/// The stride of the sliding window for temporal dimension.
709+
@noDerivative let stride: Int32
710+
/// The padding algorithm for pooling.
711+
@noDerivative let padding: Padding
712+
713+
/// Creates a max pooling layer.
714+
///
715+
/// - Parameters:
716+
/// - poolSize: The size of the sliding reduction window for pooling.
717+
/// - stride: The stride of the sliding window for temporal dimension.
718+
/// - padding: The padding algorithm for pooling.
719+
public init(
720+
poolSize: Int,
721+
stride: Int,
722+
padding: Padding
723+
) {
724+
self.poolSize = Int32(poolSize)
725+
self.stride = Int32(stride)
726+
self.padding = padding
727+
}
728+
729+
/// Returns the output obtained from applying the layer to the given input.
730+
///
731+
/// - Parameters:
732+
/// - input: The input to the layer.
733+
/// - context: The contextual information for the layer application, e.g. the current learning
734+
/// phase.
735+
/// - Returns: The output.
736+
@differentiable
737+
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
738+
return input.expandingShape(at: 1).maxPooled(
739+
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding
740+
).squeezingShape(at: 1)
741+
}
742+
}
743+
586744
/// A max pooling layer for spatial data.
587745
@_fixed_layout
588746
public struct MaxPool2D<Scalar: TensorFlowFloatingPoint>: Layer {
@@ -629,7 +787,48 @@ public struct MaxPool2D<Scalar: TensorFlowFloatingPoint>: Layer {
629787
@differentiable
630788
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
631789
return input.maxPooled(
632-
kernelSize: poolSize, strides: strides, padding: padding)
790+
kernelSize: poolSize, strides: strides, padding: padding)
791+
}
792+
}
793+
794+
/// An average pooling layer for temporal data.
795+
@_fixed_layout
796+
public struct AvgPool1D<Scalar: TensorFlowFloatingPoint>: Layer {
797+
/// The size of the sliding reduction window for pooling.
798+
@noDerivative let poolSize: Int32
799+
/// The stride of the sliding window for temporal dimension.
800+
@noDerivative let stride: Int32
801+
/// The padding algorithm for pooling.
802+
@noDerivative let padding: Padding
803+
804+
/// Creates an average pooling layer.
805+
///
806+
/// - Parameters:
807+
/// - poolSize: The size of the sliding reduction window for pooling.
808+
/// - stride: The stride of the sliding window for temporal dimension.
809+
/// - padding: The padding algorithm for pooling.
810+
public init(
811+
poolSize: Int,
812+
stride: Int,
813+
padding: Padding
814+
) {
815+
self.poolSize = Int32(poolSize)
816+
self.stride = Int32(stride)
817+
self.padding = padding
818+
}
819+
820+
/// Returns the output obtained from applying the layer to the given input.
821+
///
822+
/// - Parameters:
823+
/// - input: The input to the layer.
824+
/// - context: The contextual information for the layer application, e.g. the current learning
825+
/// phase.
826+
/// - Returns: The output.
827+
@differentiable
828+
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
829+
return input.expandingShape(at: 1).averagePooled(
830+
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding
831+
).squeezingShape(at: 1)
633832
}
634833
}
635834

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 DeepLearning
17+
18+
final class LayerTests: XCTestCase {
19+
func testConv1D() {
20+
let filter = Tensor<Float>(ones: [3, 1, 2]) * Tensor<Float>([[[0.33333333, 1]]])
21+
let bias = Tensor<Float>([0, 1])
22+
let layer = Conv1D<Float>(filter: filter, bias: bias, activation: identity, stride: 1, padding: .valid)
23+
let input = Tensor<Float>([[0, 1, 2, 3, 4], [10, 11, 12, 13, 14]]).expandingShape(at: 2)
24+
let output = layer.inferring(from: input)
25+
let expected = Tensor<Float>([[[1, 4], [2, 7], [3, 10]], [[11, 34], [12, 37], [13, 40]]])
26+
XCTAssertEqual(round(output), expected)
27+
}
28+
29+
func testMaxPool1D() {
30+
let layer = MaxPool1D<Float>(poolSize: 3, stride: 1, padding: .valid)
31+
let input = Tensor<Float>([[0, 1, 2, 3, 4], [10, 11, 12, 13, 14]]).expandingShape(at: 2)
32+
let output = layer.inferring(from: input)
33+
let expected = Tensor<Float>([[[2], [3], [4]], [[12], [13], [14]]])
34+
XCTAssertEqual(round(output), expected)
35+
}
36+
37+
func testAvgPool1D() {
38+
let layer = AvgPool1D<Float>(poolSize: 3, stride: 1, padding: .valid)
39+
let input = Tensor<Float>([[0, 1, 2, 3, 4], [10, 11, 12, 13, 14]]).expandingShape(at: 2)
40+
let output = layer.inferring(from: input)
41+
let expected = Tensor<Float>([[[1], [2], [3]], [[11], [12], [13]]])
42+
XCTAssertEqual(round(output), expected)
43+
}
44+
45+
static var allTests = [
46+
("testConv1D", testConv1D), ("testMaxPool1D", testMaxPool1D), ("testAvgPool1D", testAvgPool1D)
47+
]
48+
}

Tests/DeepLearningTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public func allTests() -> [XCTestCaseEntry] {
2121
testCase(PRNGTests.allTests),
2222
testCase(TrivialModelTests.allTests),
2323
testCase(SequentialTests.allTests),
24+
testCase(LayerTests.allTests),
2425
]
2526
}
2627
#endif

0 commit comments

Comments
 (0)