Skip to content

Commit

Permalink
Add AvgPool1D layer (#97)
Browse files Browse the repository at this point in the history
* Add AvgPool1D layer

* Remove dataFormat; Update poolSize and strides types; Update with recent changes

* Change strides property type and default value
  • Loading branch information
mkaze authored Jun 13, 2021
1 parent 09ca865 commit e6ed4a9
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2020 JetBrains s.r.o. and Kotlin Deep Learning project contributors. All Rights Reserved.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/

package org.jetbrains.kotlinx.dl.api.core.layer.pooling

import org.jetbrains.kotlinx.dl.api.core.KGraph
import org.jetbrains.kotlinx.dl.api.core.layer.Layer
import org.jetbrains.kotlinx.dl.api.core.layer.convolutional.ConvPadding
import org.jetbrains.kotlinx.dl.api.core.shape.convOutputLength
import org.tensorflow.Operand
import org.tensorflow.Shape
import org.tensorflow.op.Ops
import org.tensorflow.op.core.Squeeze

/**
* Average pooling operation for 1D temporal data (e.g. audio, timseries).
*
* Downsamples the input by taking the average over a temporal window of size [poolSize].
*
* @property [poolSize] Size of the temporal pooling window for each dimension of input.
* @property [strides] The amount of shift for pooling window per each input dimension in each pooling step.
* @property [padding] Padding strategy; can be either of [ConvPadding.VALID] which means no
* padding, or [ConvPadding.SAME] which means padding the input equally such that the output
* has the same dimension as the input.
*/
public class AvgPool1D(
public val poolSize: LongArray = longArrayOf(1, 2, 1),
public val strides: LongArray = longArrayOf(1, 2, 1),
public val padding: ConvPadding = ConvPadding.VALID,
name: String = ""
) : Layer(name) {

override val hasActivation: Boolean
get() = false
override val paramCount: Int
get() = 0
override var weights: Map<String, Array<*>>
get() = emptyMap()
set(value) = assignWeights(value)

init {
require(poolSize.size == 3) {
"The poolSize should be an array of size 3."
}

require(strides.size == 3) {
"The strides should be an array of size 3."
}

require(padding == ConvPadding.VALID || padding == ConvPadding.SAME) {
"The padding should be either of ${ConvPadding.VALID} or ${ConvPadding.SAME}."
}
}

override fun build(tf: Ops, kGraph: KGraph, inputShape: Shape) {}

override fun computeOutputShape(inputShape: Shape): Shape {
var steps = inputShape.size(1)
steps = convOutputLength(steps, poolSize[1].toInt(), padding, strides[1].toInt())
return Shape.make(inputShape.size(0), steps, inputShape.size(2))
}

override fun forward(
tf: Ops,
input: Operand<Float>,
isTraining: Operand<Boolean>,
numberOfLosses: Operand<Float>?
): Operand<Float> {
val expandAxis = 2
val tfInput = tf.expandDims(input, tf.constant(expandAxis))
val tfPoolSize = longArrayOf(1, 1, 1, 1)
val tfStrides = longArrayOf(1, 1, 1, 1)
tfPoolSize[expandAxis-1] = poolSize[1]
tfStrides[expandAxis-1] = strides[1]
val tfPadding = padding.paddingName

val avgPool = tf.nn.avgPool(
tfInput,
tfPoolSize.toList(),
tfStrides.toList(),
tfPadding
)
return tf.squeeze(avgPool, Squeeze.axis(listOf(expandAxis.toLong())))
}

override fun toString(): String =
"AvgPool1D(poolSize=$poolSize, strides=$strides, padding=$padding)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal const val LAYER_INPUT: String = "InputLayer"
internal const val LAYER_MAX_POOL_1D: String = "MaxPooling1D"
internal const val LAYER_MAX_POOLING_2D: String = "MaxPooling2D"
internal const val LAYER_MAX_POOLING_3D: String = "MaxPooling3D"
internal const val LAYER_AVG_POOL_1D: String = "AveragePooling1D"
internal const val LAYER_AVG_POOLING_2D: String = "AvgPooling2D"
internal const val LAYER_AVERAGE_POOLING_2D: String = "AveragePooling2D"
internal const val LAYER_RESCALING: String = "Rescaling"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ private fun convertToLayer(
kerasLayer.config!!,
kerasLayer.config.name!!
)
LAYER_AVG_POOL_1D -> createAvgPool1D(kerasLayer.config!!, kerasLayer.config.name!!)
LAYER_MAX_POOLING_3D -> createMaxPooling3D(
kerasLayer.config!!,
kerasLayer.config.name!!
Expand Down Expand Up @@ -650,6 +651,19 @@ private fun createMaxPooling2D(config: LayerConfig, name: String): MaxPool2D {
return MaxPool2D(addedOnesPoolSize, addedOnesStrides, padding = convertPadding(config.padding!!), name = name)
}

private fun createAvgPool1D(config: LayerConfig, name: String): Layer {
val poolSize = config.pool_size!!
val addedOnesPoolSize = longArrayOf(1, poolSize[0].toLong(), 1)
val strides = config.strides!!
val addedOnesStrides = longArrayOf(1, strides[0].toLong(), 1)
return AvgPool1D(
poolSize = addedOnesPoolSize,
strides = addedOnesStrides,
padding = convertPadding(config.padding!!),
name = name
)
}

private fun createAvgPooling2D(config: LayerConfig, name: String): AvgPool2D {
val poolSize = config.pool_size!!.toIntArray()
val addedOnesPoolSize = IntArray(4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ private fun convertToKerasLayer(layer: Layer, isKerasFullyCompatible: Boolean, i
is MaxPool1D -> createKerasMaxPool1D(layer)
is MaxPool2D -> createKerasMaxPooling2D(layer)
is MaxPool3D -> createKerasMaxPooling3D(layer)
is AvgPool1D -> createKerasAvgPool1D(layer)
is AvgPool2D -> createKerasAvgPooling2D(layer)
is Dense -> createKerasDense(layer, isKerasFullyCompatible)
is ZeroPadding2D -> createKerasZeroPadding2D(layer)
Expand Down Expand Up @@ -454,6 +455,17 @@ private fun createKerasMaxPooling2D(layer: MaxPool2D): KerasLayer {
return KerasLayer(class_name = LAYER_MAX_POOLING_2D, config = configX)
}

private fun createKerasAvgPool1D(layer: AvgPool1D): KerasLayer {
val configX = LayerConfig(
dtype = DATATYPE_FLOAT32,
pool_size = listOf(layer.poolSize[1].toInt()),
strides = listOf(layer.strides[1].toInt()),
padding = convertPadding(layer.padding),
name = layer.name
)
return KerasLayer(class_name = LAYER_AVG_POOL_1D, config = configX)
}

private fun createKerasMaxPooling3D(layer: MaxPool3D): KerasLayer {
val poolSize = mutableListOf(layer.poolSize[1], layer.poolSize[3])
val strides = mutableListOf(layer.strides[1] , layer.strides[3])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright 2020 JetBrains s.r.o. and Kotlin Deep Learning project contributors. All Rights Reserved.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/

package org.jetbrains.kotlinx.dl.api.core.layer

import org.jetbrains.kotlinx.dl.api.core.KGraph
import org.jetbrains.kotlinx.dl.api.core.layer.convolutional.ConvPadding
import org.jetbrains.kotlinx.dl.api.core.layer.pooling.AvgPool1D
import org.jetbrains.kotlinx.dl.api.core.shape.toIntArray
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.tensorflow.EagerSession
import org.tensorflow.Graph
import org.tensorflow.Shape
import org.tensorflow.op.Ops

const val EPS: Float = 1e-6f

internal class AvgPool1DTest {
@Test
fun default() {
val input = arrayOf(
arrayOf(
floatArrayOf(1.0f, -2.0f, 3.0f),
floatArrayOf(0.5f, 2.0f, 5.0f),
floatArrayOf(-1.0f, 3.0f, 2.0f),
floatArrayOf(1.5f, -1.0f, 0.5f)
),
arrayOf(
floatArrayOf(5.0f, 3.0f, 1.0f),
floatArrayOf(6.0f, -2.5f, 4.0f),
floatArrayOf(7.0f, 0.0f, 5.0f),
floatArrayOf(1.0f, 2.0f, 4.0f)
),
)
val expected = arrayOf(
arrayOf(
floatArrayOf(0.75f, 0.0f, 4.0f),
floatArrayOf(0.25f, 1.0f, 1.25f)
),
arrayOf(
floatArrayOf(5.5f, 0.25f, 2.5f),
floatArrayOf(4.0f, 1.0f, 4.5f)
)
)
val layer = AvgPool1D()

EagerSession.create().use {
val tf = Ops.create()
val inputShape = Shape.make(input.size.toLong(), input[0].size.toLong(), input[0][0].size.toLong())
layer.build(tf, KGraph(Graph().toGraphDef()), inputShape)

val inputOp = tf.constant(input)
val isTraining = tf.constant(true)
val numberOfLosses = tf.constant(1.0f)
val output = layer.forward(tf, inputOp, isTraining, numberOfLosses).asOutput()

// Check output shape is correct.
val expectedShape = intArrayOf(input.size, 2, input[0][0].size)
Assertions.assertArrayEquals(
expectedShape,
output.shape().toIntArray()
)

// Check output values are correct.
val actual = Array(input.size) { Array(2) { FloatArray(input[0][0].size) } }
output.tensor().copyTo(actual)
for (i in expected.indices) {
for (j in expected[i].indices) {
Assertions.assertArrayEquals(
expected[i][j],
actual[i][j],
EPS
)
}
}
}
}

@Test
fun withPaddingAndStride() {
val input = arrayOf(
arrayOf(
floatArrayOf(1.0f, -2.0f, 3.0f),
floatArrayOf(0.5f, 2.0f, 5.0f),
floatArrayOf(-1.0f, 3.0f, 2.0f),
floatArrayOf(1.5f, -1.0f, 0.5f)
),
arrayOf(
floatArrayOf(5.0f, 3.0f, 1.0f),
floatArrayOf(6.0f, -2.5f, 4.0f),
floatArrayOf(7.0f, 0.0f, 5.0f),
floatArrayOf(1.0f, 2.0f, 4.0f)
),
)
val expected = arrayOf(
arrayOf(
floatArrayOf(0.75f, 0.0f, 4.0f),
floatArrayOf(-0.25f, 2.5f, 3.5f),
floatArrayOf(0.25f, 1.0f, 1.25f),
floatArrayOf(1.5f, -1.0f, 0.5f),
),
arrayOf(
floatArrayOf(5.5f, 0.25f, 2.5f),
floatArrayOf(6.5f, -1.25f, 4.5f),
floatArrayOf(4.0f, 1.0f, 4.5f),
floatArrayOf(1.0f, 2.0f, 4.0f)
)
)
val layer = AvgPool1D(strides = longArrayOf(1, 1, 1), padding = ConvPadding.SAME)

EagerSession.create().use {
val tf = Ops.create()
val inputShape = Shape.make(input.size.toLong(), input[0].size.toLong(), input[0][0].size.toLong())
layer.build(tf, KGraph(Graph().toGraphDef()), inputShape)

val inputOp = tf.constant(input)
val isTraining = tf.constant(true)
val numberOfLosses = tf.constant(1.0f)
val output = layer.forward(tf, inputOp, isTraining, numberOfLosses).asOutput()

// Check output shape is correct.
val expectedShape = intArrayOf(input.size, input[0].size, input[0][0].size)
Assertions.assertArrayEquals(
expectedShape,
output.shape().toIntArray()
)

// Check output values are correct.
val actual = Array(input.size) { Array(input[0].size) { FloatArray(input[0][0].size) } }
output.tensor().copyTo(actual)
for (i in expected.indices) {
for (j in expected[i].indices) {
Assertions.assertArrayEquals(
expected[i][j],
actual[i][j],
EPS
)
}
}
}
}

@Test
fun withPoolSizeAndStride() {
val input = arrayOf(
arrayOf(
floatArrayOf(1.0f, -2.0f, 3.0f),
floatArrayOf(0.5f, 2.0f, 5.0f),
floatArrayOf(-1.0f, 3.0f, 2.0f),
floatArrayOf(1.5f, -1.0f, 0.5f)
),
arrayOf(
floatArrayOf(5.0f, 3.0f, 1.0f),
floatArrayOf(6.0f, -2.5f, 4.0f),
floatArrayOf(7.0f, 0.0f, 5.0f),
floatArrayOf(1.0f, 2.0f, 4.0f)
),
)
val expected = arrayOf(
arrayOf(
floatArrayOf(0.5f/3, 1.0f, 10.0f/3),
floatArrayOf(1.0f/3, 4.0f/3, 2.5f),
),
arrayOf(
floatArrayOf(6.0f, 0.5f/3, 10.0f/3),
floatArrayOf(14.0f/3, -0.5f/3, 13.0f/3),
)
)
val layer = AvgPool1D(poolSize = longArrayOf(1, 3, 1), strides = longArrayOf(1, 1, 1))

EagerSession.create().use {
val tf = Ops.create()
val inputShape = Shape.make(input.size.toLong(), input[0].size.toLong(), input[0][0].size.toLong())
layer.build(tf, KGraph(Graph().toGraphDef()), inputShape)

val inputOp = tf.constant(input)
val isTraining = tf.constant(true)
val numberOfLosses = tf.constant(1.0f)
val output = layer.forward(tf, inputOp, isTraining, numberOfLosses).asOutput()

// Check output shape is correct.
val expectedShape = intArrayOf(input.size, 2, input[0][0].size)
Assertions.assertArrayEquals(
expectedShape,
output.shape().toIntArray()
)

// Check output values are correct.
val actual = Array(input.size) { Array(2) { FloatArray(input[0][0].size) } }
output.tensor().copyTo(actual)
for (i in expected.indices) {
for (j in expected[i].indices) {
Assertions.assertArrayEquals(
expected[i][j],
actual[i][j],
EPS
)
}
}
}
}
}

0 comments on commit e6ed4a9

Please sign in to comment.