Skip to content
This repository was archived by the owner on Jul 1, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 202 additions & 5 deletions Sources/DeepLearning/Layer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,125 @@ public extension Dense {
}
}

/// A 1-D convolution layer (e.g. temporal convolution over a time-series).
///
/// This layer creates a convolution filter that is convolved with the layer input to produce a
/// tensor of outputs.
@_fixed_layout
public struct Conv1D<Scalar: TensorFlowFloatingPoint>: Layer {
/// The 3-D convolution kernel.
public var filter: Tensor<Scalar>
/// The bias vector.
public var bias: Tensor<Scalar>
/// An activation function.
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
/// The element-wise activation function.
@noDerivative public let activation: Activation
/// The stride of the sliding window for temporal dimension.
@noDerivative public let stride: Int32
/// The padding algorithm for convolution.
@noDerivative public let padding: Padding

/// Creates a `Conv1D` layer with the specified filter, bias, activation function, stride, and
/// padding.
///
/// - Parameters:
/// - filter: The filter (width, inputChannels, outputChannels).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the - Parameters: doc comment of memberwise initializers, please copy the doc comments from each individual stored property.

For example:

/// - Parameters:
///   - filter: The 3-D convolution kernel.
///   - bias: The bias vector.
///   ...

Please also make this change elsewhere, including for Conv2D.

/// - bias: The bias (dimensions: output channels).
/// - activation: The activation activation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// - activation: The activation activation.
/// - activation: The element-wise activation function.

Please also make this change elsewhere, including for Conv2D.

/// - stride: The stride.
/// - padding: The padding.
public init(
filter: Tensor<Scalar>,
bias: Tensor<Scalar>,
activation: @escaping Activation,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using identity as the default activation in Dense and Conv2D.

Suggested change
activation: @escaping Activation,
activation: @escaping Activation = identity,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The init method in the struct declaration doesn't have a default. Only the ones in the extensions have default values. So this is consistent, but maybe not what is desired?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you are right!

stride: Int,
padding: Padding
) {
self.filter = filter
self.bias = bias
self.activation = activation
self.stride = Int32(stride)
self.padding = padding
}

/// Returns the output obtained from applying the layer to the given input.
///
/// - Parameters:
/// - input: The input to the layer (batchCount, width, inputChannels).
/// - context: The contextual information for the layer application, e.g. the current learning
/// phase.
/// - Returns: The output (batchCount, newWidth, outputChannels).
@differentiable
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
let conv2D = input.expandingShape(at: 1).convolved2D(withFilter: filter.expandingShape(at: 0),
strides: (1, 1, stride, 1), padding: padding)
return activation(conv2D.squeezingShape(at: 1) + bias)
}
}

public extension Conv1D where Scalar.RawSignificand: FixedWidthInteger {
/// Creates a `Conv1D` layer with the specified filter shape, stride, padding, and
/// element-wise activation function. The filter tensor is initialized using Glorot uniform
/// initialization with the specified generator. The bias vector is initialized with zeros.
///
/// - Parameters:
/// - filterShape: The shape of the filter (width, inputChannels, outputChannels).
/// - stride: The stride.
/// - padding: The padding.
/// - activation: The activation function.
/// - generator: The random number generator for initialization.
///
/// - Note: Use `init(filterShape:stride:padding:activation:seed:)` for faster random
/// initialization.
init<G: RandomNumberGenerator>(
filterShape: (Int, Int, Int),
stride: Int = 1,
padding: Padding = .valid,
activation: @escaping Activation = identity,
generator: inout G
) {
let filterTensorShape = TensorShape([
Int32(filterShape.0), Int32(filterShape.1), Int32(filterShape.2)])
self.init(
filter: Tensor(glorotUniform: filterTensorShape),
bias: Tensor(zeros: TensorShape([Int32(filterShape.2)])),
activation: activation,
stride: stride,
padding: padding)
}
}

public extension Conv1D {
/// Creates a `Conv1D` layer with the specified filter shape, strides, padding, and
/// element-wise activation function. The filter tensor is initialized using Glorot uniform
/// initialization with the specified seed. The bias vector is initialized with zeros.
///
/// - Parameters:
/// - filterShape: The shape of the filter (width, inputChannels, outputChannels).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use `[dim0, dim1, ...]` syntax for specifying all shapes in doc comments.

Suggested change
/// - filterShape: The shape of the filter (width, inputChannels, outputChannels).
/// - filterShape: The 3-D shape of the filter, representing
/// `[width, inputChannels, outputChannels]`.

/// - stride: The stride.
/// - padding: The padding.
/// - activation: The activation function.
/// - seed: The random seed for initialization. The default value is random.
init(
filterShape: (Int, Int, Int),
stride: Int = 1,
padding: Padding = .valid,
activation: @escaping Activation = identity,
seed: (Int64, Int64) = (Int64.random(in: Int64.min..<Int64.max),
Int64.random(in: Int64.min..<Int64.max))
) {
let filterTensorShape = TensorShape([
Int32(filterShape.0), Int32(filterShape.1), Int32(filterShape.2)])
self.init(
filter: Tensor(glorotUniform: filterTensorShape, seed: seed),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent by 4 from self.

bias: Tensor(zeros: TensorShape([Int32(filterShape.2)])),
activation: activation,
stride: Int32(stride),
padding: padding)
}
}

/// A 2-D convolution layer (e.g. spatial convolution over images).
///
/// This layer creates a convolution filter that is convolved with the layer input to produce a
Expand All @@ -346,8 +465,7 @@ public struct Conv2D<Scalar: TensorFlowFloatingPoint>: Layer {
public typealias Activation = @differentiable (Tensor<Scalar>) -> Tensor<Scalar>
/// The element-wise activation function.
@noDerivative public let activation: Activation
/// The strides of the sliding window for each dimension of a 4-D input.
/// Strides in non-spatial dimensions must be `1`.
/// The strides of the sliding window for spatial dimensions.
@noDerivative public let strides: (Int32, Int32)
/// The padding algorithm for convolution.
@noDerivative public let padding: Padding
Expand Down Expand Up @@ -434,9 +552,6 @@ public extension Conv2D {
/// - padding: The padding.
/// - activation: The activation function.
/// - seed: The random seed for initialization. The default value is random.
///
/// - Note: Use `init(filterShape:strides:padding:activation:seed:)` for faster random
/// initialization.
init(
filterShape: (Int, Int, Int, Int),
strides: (Int, Int) = (1, 1),
Expand Down Expand Up @@ -582,6 +697,47 @@ public struct BatchNorm<Scalar: TensorFlowFloatingPoint>: Layer {
}
}

/// A max pooling layer for temporal data.
@_fixed_layout
public struct MaxPool1D<Scalar: TensorFlowFloatingPoint>: Layer {
/// The size of the sliding reduction window for pooling.
@noDerivative let poolSize: Int32
/// The stride of the sliding window for temporal dimension.
@noDerivative let stride: Int32
/// The padding algorithm for pooling.
@noDerivative let padding: Padding

/// Creates a max pooling layer.
///
/// - Parameters:
/// - poolSize: Factor by which to downscale.
/// - stride: The stride.
/// - padding: The padding.
public init(
poolSize: Int,
stride: Int,
padding: Padding
) {
self.poolSize = Int32(poolSize)
self.stride = Int32(stride)
self.padding = padding
}

/// Returns the output obtained from applying the layer to the given input.
///
/// - Parameters:
/// - input: The input to the layer.
/// - context: The contextual information for the layer application, e.g. the current learning
/// phase.
/// - Returns: The output.
@differentiable
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
return input.expandingShape(at: 1).maxPooled(
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding

).squeezingShape(at: 1)
}
}

/// A max pooling layer for spatial data.
@_fixed_layout
public struct MaxPool2D<Scalar: TensorFlowFloatingPoint>: Layer {
Expand Down Expand Up @@ -632,6 +788,47 @@ public struct MaxPool2D<Scalar: TensorFlowFloatingPoint>: Layer {
}
}

/// An average pooling layer for temporal data.
@_fixed_layout
public struct AvgPool1D<Scalar: TensorFlowFloatingPoint>: Layer {
/// The size of the sliding reduction window for pooling.
@noDerivative let poolSize: Int32
/// The stride of the sliding window for temporal dimension.
@noDerivative let stride: Int32
/// The padding algorithm for pooling.
@noDerivative let padding: Padding

/// Creates an average pooling layer.
///
/// - Parameters:
/// - poolSize: Factor by which to downscale.
/// - stride: The stride.
/// - padding: The padding.
public init(
poolSize: Int,
stride: Int,
padding: Padding
) {
self.poolSize = Int32(poolSize)
self.stride = Int32(stride)
self.padding = padding
}

/// Returns the output obtained from applying the layer to the given input.
///
/// - Parameters:
/// - input: The input to the layer.
/// - context: The contextual information for the layer application, e.g. the current learning
/// phase.
/// - Returns: The output.
@differentiable
public func applied(to input: Tensor<Scalar>, in _: Context) -> Tensor<Scalar> {
return input.expandingShape(at: 1).averagePooled(
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding
kernelSize: (1, 1, poolSize, 1), strides: (1, 1, stride, 1), padding: padding

).squeezingShape(at: 1)
}
}

/// An average pooling layer for spatial data.
@_fixed_layout
public struct AvgPool2D<Scalar: TensorFlowFloatingPoint>: Layer {
Expand Down
48 changes: 48 additions & 0 deletions Tests/DeepLearningTests/LayerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import XCTest
@testable import DeepLearning

final class LayerTests: XCTestCase {
func testConv1D() {
let filter = Tensor<Float>(ones: [3, 1, 2]) * Tensor<Float>([[[0.33333333, 1]]])
let bias = Tensor<Float>([0, 1])
let layer = Conv1D<Float>(filter: filter, bias: bias, activation: identity, stride: 1, padding: .valid)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fit within 100 columns.

let input = Tensor<Float>([[0, 1, 2, 3, 4], [10, 11, 12, 13, 14]]).expandingShape(at: 2)
let output = layer.inferring(from: input)
let expected = Tensor<Float>([[[1, 4], [2, 7], [3, 10]], [[11, 34], [12, 37], [13, 40]]])
XCTAssertEqual(round(output), expected)
}

func testMaxPool1D() {
let layer = MaxPool1D<Float>(poolSize: 3, stride: 1, padding: .valid)
let input = Tensor<Float>([[0, 1, 2, 3, 4], [10, 11, 12, 13, 14]]).expandingShape(at: 2)
let output = layer.inferring(from: input)
let expected = Tensor<Float>([[[2], [3], [4]], [[12], [13], [14]]])
XCTAssertEqual(round(output), expected)
}

func testAvgPool1D() {
let layer = AvgPool1D<Float>(poolSize: 3, stride: 1, padding: .valid)
let input = Tensor<Float>([[0, 1, 2, 3, 4], [10, 11, 12, 13, 14]]).expandingShape(at: 2)
let output = layer.inferring(from: input)
let expected = Tensor<Float>([[[1], [2], [3]], [[11], [12], [13]]])
XCTAssertEqual(round(output), expected)
}

static var allTests = [
("testConv1D", testConv1D), ("testMaxPool1D", testMaxPool1D), ("testAvgPool1D", testAvgPool1D)
]
}
1 change: 1 addition & 0 deletions Tests/DeepLearningTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public func allTests() -> [XCTestCaseEntry] {
testCase(PRNGTests.allTests),
testCase(TrivialModelTests.allTests),
testCase(SequentialTests.allTests),
testCase(LayerTests.allTests),
]
}
#endif