diff --git a/Tests/DeepLearningTests/InitializerTests.swift b/Tests/DeepLearningTests/InitializerTests.swift new file mode 100644 index 000000000..ced975f98 --- /dev/null +++ b/Tests/DeepLearningTests/InitializerTests.swift @@ -0,0 +1,83 @@ +// 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 InitializerTests: XCTestCase { + func testInitializers() { + let scalar = Tensor(1) + let matrix: Tensor = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]] + let broadcastScalar = Tensor(broadcasting: 10, rank: 3) + let some4d = Tensor(shape: [2, 1, 2, 1], scalars: [2, 3, 4, 5]) + XCTAssertEqual(ShapedArray(shape: [2, 1, 2, 1], scalars: [2, 3, 4, 5]), some4d.array) + XCTAssertEqual(ShapedArray(shape: [], scalars: [1]), scalar.array) + XCTAssertEqual(ShapedArray(shape: [2, 3], scalars: [1, 2, 3, 4, 5, 6]), matrix.array) + XCTAssertEqual(ShapedArray(shape: [1, 1, 1], scalars: [10]), broadcastScalar.array) + } + + func testFactoryInitializers() { + let x = Tensor(ones: [1, 10]) + XCTAssertEqual(ShapedArray(repeating: 1, shape: [1, 10]), x.array) + } + + func testNumericInitializers() { + let x = Tensor(oneHotAtIndices: [0, 2, -1, 1], depth: 3) + XCTAssertEqual(ShapedArray( + shape: [4, 3], + scalars: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]), x.array) + } + + func testScalarToTensorConversion() { + let tensor = Tensor(broadcasting: 42, rank: 4) + XCTAssertEqual([1, 1, 1, 1], tensor.shape) + XCTAssertEqual([42], tensor.scalars) + } + + func testArrayConversion() { + let array3D = ShapedArray(repeating: 1.0, shape: [2, 3, 4]) + let tensor3D = Tensor(array3D) + XCTAssertEqual(array3D, tensor3D.array) + } + + func testDataTypeCast() { + let x = Tensor(ones: [5, 5]) + let ints = Tensor(x) + let floats = Tensor(x) + let u32s = Tensor(floats) + XCTAssertEqual(ShapedArray(repeating: 1, shape: [5, 5]), ints.array) + XCTAssertEqual(ShapedArray(repeating: 1, shape: [5, 5]), floats.array) + XCTAssertEqual(ShapedArray(repeating: 1, shape: [5, 5]), u32s.array) + } + + func testBoolToNumericCast() { + let bools = Tensor(shape: [2, 2], scalars: [true, false, true, false]) + let ints = Tensor(bools) + let floats = Tensor(bools) + let i8s = Tensor(bools) + XCTAssertEqual(ShapedArray(shape: [2, 2], scalars: [1, 0, 1, 0]), ints.array) + XCTAssertEqual(ShapedArray(shape: [2, 2], scalars: [1, 0, 1, 0]), floats.array) + XCTAssertEqual(ShapedArray(shape: [2, 2], scalars: [1, 0, 1, 0]), i8s.array) + } + + static var allTests = [ + ("testInitializers", testInitializers), + ("testFactoryInitializers", testFactoryInitializers), + ("testNumericInitializers", testNumericInitializers), + ("testScalarToTensorConversion", testScalarToTensorConversion), + ("testArrayConversion", testArrayConversion), + ("testDataTypeCast", testDataTypeCast), + ("testBoolToNumericCast", testBoolToNumericCast) + ] +} diff --git a/Tests/DeepLearningTests/LayerTests.swift b/Tests/DeepLearningTests/LayerTests.swift index ca57ec304..77ab1cdb7 100644 --- a/Tests/DeepLearningTests/LayerTests.swift +++ b/Tests/DeepLearningTests/LayerTests.swift @@ -199,7 +199,7 @@ final class LayerTests: XCTestCase { [[ 0.066890605, 0.049586136, 0.024610005, 0.09341654]], [[ 0.065792546, 0.009325638, 0.06439907, 0.114802904]], [[ 0.055909205, 0.00035158166, 0.054020774, 0.09812111]]]) - let (𝛁rnn, 𝛁inputs) = pullback(.init(inputs.map { SimpleRNNCell.State($0) })) + let (𝛁rnn, _) = pullback(.init(inputs.map { SimpleRNNCell.State($0) })) XCTAssertEqual(𝛁rnn.cell.weight, [[ 0.0, 0.0, 0.0, 0.0], [-0.0051169936, 0.0014167001, 0.0074189613, 0.017496519], diff --git a/Tests/DeepLearningTests/OperatorTests/BasicTests.swift b/Tests/DeepLearningTests/OperatorTests/BasicTests.swift new file mode 100644 index 000000000..860d87386 --- /dev/null +++ b/Tests/DeepLearningTests/OperatorTests/BasicTests.swift @@ -0,0 +1,488 @@ +// 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 + +infix operator ++: AdditionPrecedence +infix operator .= + +infix operator ..: StridedRangeFormationPrecedence +precedencegroup StridedRangeFormationPrecedence { + associativity: left + higherThan: CastingPrecedence + lowerThan: RangeFormationPrecedence +} + +final class BasicOperatorTests: XCTestCase { + func testElementIndexing() { + // NOTE: cannot test multiple `Tensor.shape` or `Tensor.scalars` directly + // until send and receive are implemented (without writing a bunch of mini + // tests). Instead, `Tensor.array` is called to make a ShapedArray host copy + // and the ShapedArray is tested. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let element2D = tensor3D[2] + let element1D = tensor3D[1][3] + let element0D = tensor3D[2][0][3] + + let array2D = element2D.array + let array1D = element1D.array + let array0D = element0D.array + + /// Test shapes + XCTAssertEqual([4, 5], array2D.shape) + XCTAssertEqual([5], array1D.shape) + XCTAssertEqual([], array0D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 40.0, to: 60, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 35.0, to: 40, by: 1)), array1D.scalars) + XCTAssertEqual([43], array0D.scalars) + } + + func testElementIndexingAssignment() { + // NOTE: cannot test multiple `Tensor.shape` or `Tensor.scalars` directly + // until send and receive are implemented (without writing a bunch of mini + // tests). Instead, `Tensor.array` is called to make a ShapedArray host copy + // and the ShapedArray is tested. + var tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + tensor3D[2] = Tensor( + shape: [4, 5], scalars: Array(stride(from: 20.0, to: 40, by: 1))) + let element2D = tensor3D[2] + let element1D = tensor3D[1][3] + let element0D = tensor3D[2][0][3] + + let array2D = element2D.array + let array1D = element1D.array + let array0D = element0D.array + + /// Test shapes + XCTAssertEqual([4, 5], array2D.shape) + XCTAssertEqual([5], array1D.shape) + XCTAssertEqual([], array0D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 20.0, to: 40, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 35.0, to: 40, by: 1)), array1D.scalars) + XCTAssertEqual([23], array0D.scalars) + } + + func testNestedElementIndexing() { + // NOTE: This test could use a clearer name, along with other "indexing" + // tests. Note to update corresponding test names in other files + // (shaped_array.test) as well. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let element1D = tensor3D[1, 3] + let element0D = tensor3D[2, 0, 3] + + let array1D = element1D.array + let array0D = element0D.array + + /// Test shapes + XCTAssertEqual([5], array1D.shape) + XCTAssertEqual([], array0D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 35.0, to: 40, by: 1)), array1D.scalars) + XCTAssertEqual([43], array0D.scalars) + } + + func testSliceIndexing() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let slice3D = tensor3D[2...] + let slice2D = tensor3D[1][0..<2] + let slice1D = tensor3D[0][0][3..<5] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([1, 4, 5], array3D.shape) + XCTAssertEqual([2, 5], array2D.shape) + XCTAssertEqual([2], array1D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 40.0, to: 60, by: 1)), array3D.scalars) + XCTAssertEqual(Array(stride(from: 20.0, to: 30, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 3.0, to: 5, by: 1)), array1D.scalars) + } + + func testSliceIndexingAssignment() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + var tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + tensor3D[2, 0..<5, 0..<6] = Tensor( + shape: [4, 5], scalars: Array(stride(from: 20.0, to: 40, by: 1))) + let slice3D = tensor3D[2...] + let slice2D = tensor3D[1][0..<2] + let slice1D = tensor3D[0][0][3..<5] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([1, 4, 5], array3D.shape) + XCTAssertEqual([2, 5], array2D.shape) + XCTAssertEqual([2], array1D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 20.0, to: 40, by: 1)), array3D.scalars) + XCTAssertEqual(Array(stride(from: 20.0, to: 30, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 3.0, to: 5, by: 1)), array1D.scalars) + } + + func testEllipsisIndexing() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + var tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + tensor3D[2, TensorRange.ellipsis] = Tensor( + shape: [4, 5], scalars: Array(stride(from: 20.0, to: 40, by: 1))) + let slice3D = tensor3D[2..., TensorRange.ellipsis] + let slice2D = tensor3D[1][0..<2] + let slice1D = tensor3D[0][0][3..<5] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([1, 4, 5], array3D.shape) + XCTAssertEqual([2, 5], array2D.shape) + XCTAssertEqual([2], array1D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 20.0, to: 40, by: 1)), array3D.scalars) + XCTAssertEqual(Array(stride(from: 20.0, to: 30, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 3.0, to: 5, by: 1)), array1D.scalars) + } + + func testNewAxisIndexing() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let newAxis = TensorRange.newAxis + let ellipsis = TensorRange.ellipsis + let slice3D = tensor3D[2..., newAxis, ellipsis] + let slice2D = tensor3D[1, newAxis][0..<1, 0..<2] + let slice1D = tensor3D[0][newAxis, 0][0..<1, 3..<5, newAxis] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([1, 1, 4, 5], array3D.shape) + XCTAssertEqual([1, 2, 5], array2D.shape) + XCTAssertEqual([1, 2, 1], array1D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 40.0, to: 60, by: 1)), array3D.scalars) + XCTAssertEqual(Array(stride(from: 20.0, to: 30, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 3.0, to: 5, by: 1)), array1D.scalars) + } + + func testSqueezeAxisIndexing() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let newAxis = TensorRange.newAxis + let ellipsis = TensorRange.ellipsis + let squeezeAxis = TensorRange.squeezeAxis + let slice3D = tensor3D[2..., newAxis, ellipsis][squeezeAxis, squeezeAxis] + let slice2D = tensor3D[1, newAxis][squeezeAxis, 0..<2] + let slice1D = tensor3D[0..<1, 0, 3..<5, newAxis][ + squeezeAxis, ellipsis, squeezeAxis] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([4, 5], array3D.shape) + XCTAssertEqual([2, 5], array2D.shape) + XCTAssertEqual([2], array1D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 40.0, to: 60, by: 1)), array3D.scalars) + XCTAssertEqual(Array(stride(from: 20.0, to: 30, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 3.0, to: 5, by: 1)), array1D.scalars) + } + + func testStridedSliceIndexing() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let slice3D = tensor3D[2...] + let slice2D = tensor3D[1][0..<3..2] + let slice1D = tensor3D[0][0][1..<5..2] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([1, 4, 5], array3D.shape) + XCTAssertEqual([2, 5], array2D.shape) + XCTAssertEqual([2], array1D.shape) + + /// Test scalars + XCTAssertEqual(Array(stride(from: 40.0, to: 60, by: 1)), array3D.scalars) + XCTAssertEqual( + Array(stride(from: 20.0, to: 25, by: 1)) + + Array(stride(from: 30.0, to: 35, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 1.0, to: 5, by: 2)), array1D.scalars) + } + + func testStridedSliceIndexingAssignment() { + // NOTE: cannot test `Tensor.shape` or `Tensor.scalars` directly until send + // and receive are implemented (without writing a bunch of mini tests). + // Instead, `Tensor.array` is called to make a ShapedArray host copy and the + // ShapedArray is tested instead. + var tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + tensor3D[2, 0..<5..2, 0..<6] = Tensor( + shape: [2, 5], scalars: Array(stride(from: 20.0, to: 40, by: 2))) + let slice3D = tensor3D[2...] + let slice2D = tensor3D[1][0..<2] + let slice1D = tensor3D[0][0][3..<5] + + let array3D = slice3D.array + let array2D = slice2D.array + let array1D = slice1D.array + + /// Test shapes + XCTAssertEqual([1, 4, 5], array3D.shape) + XCTAssertEqual([2, 5], array2D.shape) + XCTAssertEqual([2], array1D.shape) + + /// Test scalars + XCTAssertEqual( + Array(stride(from: 20.0, to: 30, by: 2)) + + Array(stride(from: 45.0, to: 50, by: 1)) + + Array(stride(from: 30.0, to: 40, by: 2)) + + Array(stride(from: 55.0, to: 60, by: 1)), array3D.scalars) + XCTAssertEqual(Array(stride(from: 20.0, to: 30, by: 1)), array2D.scalars) + XCTAssertEqual(Array(stride(from: 3.0, to: 5, by: 1)), array1D.scalars) + } + + func testWholeTensorSlicing() { + let t: Tensor = [[[1, 1, 1], [2, 2, 2]], + [[3, 3, 3], [4, 4, 4]], + [[5, 5, 5], [6, 6, 6]]] + let slice2 = t.slice(lowerBounds: [1, 0, 0], upperBounds: [2, 1, 3]) + XCTAssertEqual(ShapedArray(shape: [1, 1, 3], scalars: [3, 3, 3]), slice2.array) + } + + func testAdvancedIndexing() { + // NOTE: cannot test multiple `Tensor.shape` or `Tensor.scalars` directly + // until send and receive are implemented (without writing a bunch of mini + // tests). Instead, `Tensor.array` is called to make a ShapedArray host copy + // and the ShapedArray is tested. + let tensor3D = Tensor( + shape: [3, 4, 5], scalars: Array(stride(from: 0.0, to: 60, by: 1))) + let element2D = tensor3D[1..<3, 0, 3...] + let array2D = element2D.array + + // Test shape + XCTAssertEqual([2, 2], array2D.shape) + + // Test scalars + XCTAssertEqual(Array([23.0, 24.0, 43.0, 44.0]), array2D.scalars) + } + + func testConcatenation() { + // 2 x 3 + let t1 = Tensor([[0, 1, 2], [3, 4, 5]]) + // 2 x 3 + let t2 = Tensor([[6, 7, 8], [9, 10, 11]]) + let concatenated = t1 ++ t2 + let concatenated0 = t1.concatenated(with: t2) + let concatenated1 = t1.concatenated(with: t2, alongAxis: 1) + XCTAssertEqual(ShapedArray(shape: [4, 3], scalars: Array(0..<12)), concatenated.array) + XCTAssertEqual(ShapedArray(shape: [4, 3], scalars: Array(0..<12)), concatenated0.array) + XCTAssertEqual( + ShapedArray(shape: [2, 6], scalars: [0, 1, 2, 6, 7, 8, 3, 4, 5, 9, 10, 11]), + concatenated1.array) + } + + func testVJPConcatenation() { + let a1 = Tensor([1,2,3,4]) + let b1 = Tensor([5,6,7,8,9,10]) + + let a2 = Tensor([1,1,1,1]) + let b2 = Tensor([1,1,1,1,1,1]) + + let grads = gradient(at: a2, b2) { a, b in + return ((a1 * a) ++ (b1 * b)).sum() + } + + XCTAssertEqual(a1, grads.0) + XCTAssertEqual(b1, grads.1) + } + + func testVJPConcatenationNegativeAxis() { + let a1 = Tensor([1,2,3,4]) + let b1 = Tensor([5,6,7,8,9,10]) + + let a2 = Tensor([1,1,1,1]) + let b2 = Tensor([1,1,1,1,1,1]) + + let grads = gradient(at: a2, b2) { a, b in + return (a1 * a).concatenated(with: b1 * b, alongAxis: -1).sum() + } + + XCTAssertEqual(a1, grads.0) + XCTAssertEqual(b1, grads.1) + } + + func testTranspose() { + // 3 x 2 -> 2 x 3 + let xT = Tensor([[1, 2], [3, 4], [5, 6]]).transposed() + let xTArray = xT.array + XCTAssertEqual(2, xTArray.rank) + XCTAssertEqual([2, 3], xTArray.shape) + XCTAssertEqual([1, 3, 5, 2, 4, 6], xTArray.scalars) + } + + func testReshape() { + // 2 x 3 -> 1 x 3 x 1 x 2 x 1 + let matrix = Tensor([[0, 1, 2], [3, 4, 5]]) + let reshaped = matrix.reshaped(to: [1, 3, 1, 2, 1]) + + XCTAssertEqual([1, 3, 1, 2, 1], reshaped.shape) + XCTAssertEqual(Array(0..<6), reshaped.scalars) + } + + func testFlatten() { + // 2 x 3 -> 6 + let matrix = Tensor([[0, 1, 2], [3, 4, 5]]) + let flattened = matrix.flattened() + + XCTAssertEqual([6], flattened.shape) + XCTAssertEqual(Array(0..<6), flattened.scalars) + } + + func testFlatten0D() { + let scalar = Tensor(5) + let flattened = scalar.flattened() + XCTAssertEqual([1], flattened.shape) + XCTAssertEqual([5], flattened.scalars) + } + + func testReshapeToScalar() { + // 1 x 1 -> scalar + let z = Tensor([[10]]).reshaped(to: []) + XCTAssertEqual([], z.shape) + } + + func testReshapeTensor() { + // 2 x 3 -> 1 x 3 x 1 x 2 x 1 + let x = Tensor(repeating: 0.0, shape: [2, 3]) + let y = Tensor(repeating: 0.0, shape: [1, 3, 1, 2, 1]) + let result = x.reshaped(like: y) + XCTAssertEqual([1, 3, 1, 2, 1], result.shape) + } + + func testUnbroadcast1() { + let x = Tensor(repeating: 1, shape: [2, 3, 4, 5]) + let y = Tensor(repeating: 1, shape: [4, 5]) + let z = x.unbroadcast(like: y) + XCTAssertEqual(ShapedArray(repeating: 6, shape: [4, 5]), z.array) + } + + func testUnbroadcast2() { + let x = Tensor(repeating: 1, shape: [2, 3, 4, 5]) + let y = Tensor(repeating: 1, shape: [3, 1, 5]) + let z = x.unbroadcast(like: y) + XCTAssertEqual(ShapedArray(repeating: 8, shape: [3, 1, 5]), z.array) + } + + func testSliceUpdate() { + var t1 = Tensor([[1, 2, 3], [4, 5, 6]]) + t1[0] = Tensor(zeros: [3]) + XCTAssertEqual(ShapedArray(shape:[2, 3], scalars: [0, 0, 0, 4, 5, 6]), t1.array) + var t2 = t1 + t2[0][2] = Tensor(3) + XCTAssertEqual(ShapedArray(shape:[2, 3], scalars: [0, 0, 3, 4, 5, 6]), t2.array) + var t3 = Tensor([[true, true, true], [false, false, false]]) + t3[0][1] = Tensor(false) + XCTAssertEqual(ShapedArray( + shape:[2, 3], scalars: [true, false, true, false, false, false]), t3.array) + var t4 = Tensor([[true, true, true], [false, false, false]]) + t4[0] = Tensor(repeating: false, shape: [3]) + XCTAssertEqual(ShapedArray(repeating: false, shape: [2, 3]), t4.array) + } + + func testBroadcastTensor() { + // 1 -> 2 x 3 x 4 + let one = Tensor(1) + var target = Tensor(repeating: 0.0, shape: [2, 3, 4]) + let broadcasted = one.broadcast(like: target) + XCTAssertEqual(Tensor(repeating: 1, shape: [2, 3, 4]), broadcasted) + target .= Tensor(repeating: 1, shape: [1, 3, 1]) + XCTAssertEqual(Tensor(repeating: 1, shape: [2, 3, 4]), target) + } + + static var allTests = [ + ("testElementIndexing", testElementIndexing), + ("testElementIndexingAssignment", testElementIndexingAssignment), + ("testNestedElementIndexing", testNestedElementIndexing), + ("testSliceIndexing", testSliceIndexing), + ("testSliceIndexingAssignment", testSliceIndexingAssignment), + ("testEllipsisIndexing", testEllipsisIndexing), + ("testNewAxisIndexing", testNewAxisIndexing), + ("testSqueezeAxisIndexing", testSqueezeAxisIndexing), + ("testStridedSliceIndexing", testStridedSliceIndexing), + ("testStridedSliceIndexingAssignment", testStridedSliceIndexingAssignment), + ("testWholeTensorSlicing", testWholeTensorSlicing), + ("testAdvancedIndexing", testAdvancedIndexing), + ("testConcatenation", testConcatenation), + ("testVJPConcatenation", testVJPConcatenation), + ("testTranspose", testTranspose), + ("testReshape", testReshape), + ("testFlatten", testFlatten), + ("testFlatten0D", testFlatten0D), + ("testReshapeToScalar", testReshapeToScalar), + ("testReshapeTensor", testReshapeTensor), + ("testUnbroadcast1", testUnbroadcast1), + ("testUnbroadcast2", testUnbroadcast2), + ("testSliceUpdate", testSliceUpdate), + ("testBroadcastTensor", testBroadcastTensor) + ] +} diff --git a/Tests/DeepLearningTests/OperatorTests/ComparisonTests.swift b/Tests/DeepLearningTests/OperatorTests/ComparisonTests.swift new file mode 100644 index 000000000..e20a9cdc9 --- /dev/null +++ b/Tests/DeepLearningTests/OperatorTests/ComparisonTests.swift @@ -0,0 +1,35 @@ +// 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 ComparisonOperatorTests: XCTestCase { + func testElementwiseComparison() { + let x = Tensor([0, 1, 2]) + let y = Tensor([2, 1, 3]) + XCTAssertEqual((x .< y).scalars, [true, false, true]) + } + + func testLexicographicalComparison() { + let x = Tensor([0, 1, 2, 3, 4]) + let y = Tensor([2, 3, 4, 5, 6]) + XCTAssertTrue(x < y) + } + + static var allTests = [ + ("testElementwiseComparison", testElementwiseComparison), + ("testLexicographicalComparison", testLexicographicalComparison) + ] +} diff --git a/Tests/DeepLearningTests/DatasetTests.swift b/Tests/DeepLearningTests/OperatorTests/DatasetTests.swift similarity index 99% rename from Tests/DeepLearningTests/DatasetTests.swift rename to Tests/DeepLearningTests/OperatorTests/DatasetTests.swift index 1c58fb5c6..54f413e50 100644 --- a/Tests/DeepLearningTests/DatasetTests.swift +++ b/Tests/DeepLearningTests/OperatorTests/DatasetTests.swift @@ -13,9 +13,9 @@ // limitations under the License. import XCTest -import DeepLearning +@testable import DeepLearning -struct SimpleOutput : TensorGroup { +struct SimpleOutput: TensorGroup { let a: TensorHandle let b: TensorHandle } diff --git a/Tests/DeepLearningTests/OperatorTests/MathTests.swift b/Tests/DeepLearningTests/OperatorTests/MathTests.swift new file mode 100644 index 000000000..166a78ed5 --- /dev/null +++ b/Tests/DeepLearningTests/OperatorTests/MathTests.swift @@ -0,0 +1,214 @@ +// 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 MathOperatorTests: XCTestCase { + func testReduction() { + // 2 x 5 + let x = Tensor([[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]) + XCTAssertEqual(Tensor(30), x.sum()) + XCTAssertEqual( + Tensor(shape: [5], scalars: [2, 4, 6, 8, 10]), + x.sum(squeezingAxes: 0)) + XCTAssertEqual( + Tensor(shape: [1, 5], scalars: [2, 4, 6, 8, 10]), + x.sum(alongAxes: 0)) + + XCTAssertEqual(Tensor(14400), x.product()) + XCTAssertEqual( + Tensor(shape: [5], scalars: [1, 4, 9, 16, 25]), + x.product(squeezingAxes: 0)) + XCTAssertEqual( + Tensor(shape: [1, 5], scalars: [1, 4, 9, 16, 25]), + x.product(alongAxes: 0)) + + XCTAssertEqual(Tensor(3), x.mean()) + XCTAssertEqual( + Tensor(shape: [5], scalars: [1, 2, 3, 4, 5]), + x.mean(squeezingAxes: 0)) + XCTAssertEqual( + Tensor(shape: [5], scalars: [1, 2, 3, 4, 5]), + x.mean(alongAxes: 0)) + XCTAssertEqual( + Tensor(shape: [2], scalars: [3, 3]), + x.mean(squeezingAxes: 1)) + XCTAssertEqual( + Tensor(shape: [1, 2], scalars: [3, 3]), + x.mean(alongAxes: 1)) + + XCTAssertEqual(Tensor(2), x.variance()) + XCTAssertEqual( + Tensor(shape: [5], scalars: [0, 0, 0, 0, 0]), + x.variance(squeezingAxes: 0)) + XCTAssertEqual( + Tensor(shape: [5], scalars: [0, 0, 0, 0, 0]), + x.variance(alongAxes: 0)) + XCTAssertEqual( + Tensor(shape: [2], scalars: [2, 2]), + x.variance(squeezingAxes: 1)) + XCTAssertEqual( + Tensor(shape: [1, 2], scalars: [2, 2]), + x.variance(alongAxes: 1)) + } + + func testArgmax() { + // 2 x 3 + let x = Tensor([[0, 1, 2], [3, 4, 5]]) + let argmax0 = x.argmax(squeezingAxis: 0) + let argmax1 = x.argmax(squeezingAxis: 1) + let scalarsArgmax = x.argmax() + XCTAssertEqual(ShapedArray(shape: [3], scalars: [1, 1, 1]), argmax0.array) + XCTAssertEqual(ShapedArray(shape: [2], scalars: [2, 2]), argmax1.array) + XCTAssertEqual(ShapedArray(shape: [], scalars: [5]), scalarsArgmax.array) + } + + func testCeilAndFloor() { + let x = Tensor([-1.3, -0.4, 0.5, 1.6]) + let xFloor = floor(x) + let xCeil = ceil(x) + XCTAssertEqual(ShapedArray(shape: [4], scalars: [-2, -1, 0, 1]), xFloor.array) + XCTAssertEqual(ShapedArray(shape: [4], scalars: [-1, 0, 1, 2]), xCeil.array) + } + + func testSimpleMath() { + let x = Tensor([1.2, 1.2]) + let y = tanh(x) + let array = y.array + XCTAssertEqual([2], array.shape) + XCTAssertEqual(0.833655, Double(array.scalars[0]), accuracy: 0.0001) + XCTAssertEqual(0.833655, Double(array.scalars[1]), accuracy: 0.0001) + } + + func testStandardDeviation() { + XCTAssertEqual(Tensor(0), Tensor([1]).standardDeviation()) + XCTAssertEqual(Tensor(0.5), Tensor([0, 1]).standardDeviation(alongAxes: 0)) + XCTAssertEqual(Tensor(0.5), Tensor([0, 1]).standardDeviation()) + XCTAssertEqual( + 2.87228132, + Tensor(rangeFrom: 0, to: 10, stride: 1).standardDeviation().scalarized(), + accuracy: 0.001) + let matrix = Tensor(rangeFrom: 0, to: 10, stride: 1).reshaped(to: [2, 5]) + XCTAssertEqual(2.87228132, matrix.standardDeviation().scalarized(), accuracy: 0.001) + let values = matrix.standardDeviation(alongAxes: 1).array.scalars + XCTAssertEqual(1.4142, Double(values[0]), accuracy: 0.0001) + XCTAssertEqual(1.4142, Double(values[1]), accuracy: 0.0001) + } + + func test3Adds() { + let a = Tensor([1]) + let b = Tensor([2]) + let c = Tensor([3]) + + let o = a + b + c + XCTAssertEqual([6], o.scalars) + } + + func testMultiOpMath() { + let x = Tensor([1.2, 1.2]) + let y = Tensor([2.4, 2.4]) + let t1 = x + y + let t2 = t1 * t1 + let t3 = sqrt(t2) + + let array1 = t1.array + let array2 = t2.array + let array3 = t3.array + XCTAssertEqual([2], array1.shape) + XCTAssertEqual([2], array2.shape) + XCTAssertEqual([2], array3.shape) + XCTAssertEqual(3.6, Double(array1.scalars[0]), accuracy: 0.0001) + XCTAssertEqual(3.6, Double(array1.scalars[1]), accuracy: 0.0001) + XCTAssertEqual(12.96, Double(array2.scalars[0]), accuracy: 0.0001) + XCTAssertEqual(12.96, Double(array2.scalars[1]), accuracy: 0.0001) + XCTAssertEqual(3.6, Double(array3.scalars[0]), accuracy: 0.0001) + XCTAssertEqual(3.6, Double(array3.scalars[1]), accuracy: 0.0001) + } + + func testXWPlusB() { + // Shape: 1 x 4 + let x = Tensor([[1.0, 2.0, 2.0, 1.0]]) + // Shape: 4 x 2 + let w = Tensor([[1.0, 0.0], [3.0, 0.0], [2.0, 3.0], [1.0, 0.0]]) + // Shape: 2 + let b = Tensor([0.5, 0.5]) + // Shape: 1 x 2 (broadcasted) + let result = matmul(x, w) + b + XCTAssertEqual([1, 2], result.shape) + XCTAssertEqual([12.5, 6.5], result.scalars) + } + + func testXORInference() { + func xor(_ x: Float, _ y: Float) -> Float { + let x = Tensor([x, y]).reshaped(to: [1, 2]) + + // FIXME: If params are declared outside of `xor`, it would crash. + // 2 x 4 + let w1 = Tensor( + [[-1.83586664, -0.20809225, 0.47667537, 1.90780607], + [-1.83523219, -0.51167348, 0.15490439, 1.91018065]]) + // 1 x 4 + let b1 = Tensor([[2.54353216, 0.25132703, -0.16503136, -0.85754058]]) + // 4 x 1 + let w2 = Tensor([[3.04350065], [0.35590511], [-0.3252157], [3.49349223]]) + // 1 x 1 + let b2 = Tensor([[-0.74635993]]) + + let o1 = tanh(matmul(x, w1) + b1) + let y = tanh(matmul(o1, w2) + b2) + return y.array.scalars[0] // TODO: use better scalar getter + } + + XCTAssertEqual(0.0, xor(0.0, 0.0), accuracy: 0.1) + XCTAssertEqual(1.0, xor(0.0, 1.0), accuracy: 0.1) + XCTAssertEqual(1.0, xor(1.0, 0.0), accuracy: 0.1) + XCTAssertEqual(0.0, xor(1.0, 1.0), accuracy: 0.1) + } + + func testMLPClassifierStruct() { + struct MLPClassifier { + // 2 x 4 + var w1 = Tensor([[1.0, 0.8, 0.4, 0.4], + [0.4, 0.3, 0.2, 0.1]]) + // 4 x 1 + var w2 = Tensor([[0.4], [0.4], [0.3], [0.9]]) + var b1 = Tensor(zeros: [1, 4]) + var b2 = Tensor(zeros: [1, 1]) + + func prediction(for x: Tensor) -> Tensor { + let o1 = tanh(matmul(x, w1) + b1) + return tanh(matmul(o1, w2) + b2) + } + } + + let input = Tensor([[1, 0.5]]) + let classifier = MLPClassifier() + let prediction = classifier.prediction(for: input) + XCTAssertEqual(0.816997, Double(prediction.scalars[0]), accuracy: 0.0001) + } + + static var allTests = [ + ("testReduction", testReduction), + ("testArgmax", testArgmax), + ("testCeilAndFloor", testCeilAndFloor), + ("testSimpleMath", testSimpleMath), + ("testStandardDeviation", testStandardDeviation), + ("test3Adds", test3Adds), + ("testMultiOpMath", testMultiOpMath), + ("testXWPlusB", testXWPlusB), + ("testXORInference", testXORInference), + ("testMLPClassifierStruct", testMLPClassifierStruct) + ] +} diff --git a/Tests/DeepLearningTests/TensorTests.swift b/Tests/DeepLearningTests/TensorTests.swift index 015254875..03218f995 100644 --- a/Tests/DeepLearningTests/TensorTests.swift +++ b/Tests/DeepLearningTests/TensorTests.swift @@ -13,7 +13,7 @@ // limitations under the License. import XCTest -import TensorFlow +@testable import DeepLearning final class TensorTests: XCTestCase { func testSimpleCond() { diff --git a/Tests/DeepLearningTests/XCTestManifests.swift b/Tests/DeepLearningTests/XCTestManifests.swift index 9f30a652e..d068f03df 100644 --- a/Tests/DeepLearningTests/XCTestManifests.swift +++ b/Tests/DeepLearningTests/XCTestManifests.swift @@ -23,7 +23,10 @@ public func allTests() -> [XCTestCaseEntry] { testCase(SequentialTests.allTests), testCase(LayerTests.allTests), testCase(TensorTests.allTests), + testCase(BasicOperatorTests.allTests), + testCase(ComparisonOperatorTests.allTests), testCase(DatasetTests.allTests), + testCase(MathOperatorTests.allTests), ] } #endif