forked from kscience/kmath
first DTW method realization
This commit is contained in:
parent
b8809d1c21
commit
55bcd202bf
@ -19,9 +19,9 @@ import org.ejml.sparse.csc.factory.DecompositionFactory_DSCC
|
|||||||
import org.ejml.sparse.csc.factory.DecompositionFactory_FSCC
|
import org.ejml.sparse.csc.factory.DecompositionFactory_FSCC
|
||||||
import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC
|
import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC
|
||||||
import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC
|
import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC
|
||||||
import space.kscience.kmath.UnstableKMathAPI
|
|
||||||
import space.kscience.kmath.linear.*
|
import space.kscience.kmath.linear.*
|
||||||
import space.kscience.kmath.linear.Matrix
|
import space.kscience.kmath.linear.Matrix
|
||||||
|
import space.kscience.kmath.UnstableKMathAPI
|
||||||
import space.kscience.kmath.nd.StructureFeature
|
import space.kscience.kmath.nd.StructureFeature
|
||||||
import space.kscience.kmath.operations.DoubleField
|
import space.kscience.kmath.operations.DoubleField
|
||||||
import space.kscience.kmath.operations.FloatField
|
import space.kscience.kmath.operations.FloatField
|
||||||
|
@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.series
|
package space.kscience.kmath.series
|
||||||
|
|
||||||
|
import space.kscience.kmath.nd.*
|
||||||
|
import space.kscience.kmath.nd.DoubleBufferND
|
||||||
|
import space.kscience.kmath.nd.ShapeND
|
||||||
|
import space.kscience.kmath.nd.ndAlgebra
|
||||||
|
import space.kscience.kmath.operations.DoubleField
|
||||||
import space.kscience.kmath.structures.DoubleBuffer
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@ -12,9 +17,11 @@ public const val LEFT_OFFSET : Int = -1
|
|||||||
public const val BOTTOM_OFFSET : Int = 1
|
public const val BOTTOM_OFFSET : Int = 1
|
||||||
public const val DIAGONAL_OFFSET : Int = 0
|
public const val DIAGONAL_OFFSET : Int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Change container for alignMatrix to kmath special ND structure
|
// TODO: Change container for alignMatrix to kmath special ND structure
|
||||||
public data class DynamicTimeWarpingData(val totalCost : Double = 0.0,
|
public data class DynamicTimeWarpingData(val totalCost : Double = 0.0,
|
||||||
val alignMatrix : Array<BooleanArray> = Array(0) {BooleanArray(0)}) {
|
val alignMatrix : IntBufferND = IntRingOpsND.structureND(ShapeND(0, 0)) { (i, j) -> 0}) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || this::class != other::class) return false
|
if (other == null || this::class != other::class) return false
|
||||||
@ -22,16 +29,9 @@ public data class DynamicTimeWarpingData(val totalCost : Double = 0.0,
|
|||||||
other as DynamicTimeWarpingData
|
other as DynamicTimeWarpingData
|
||||||
|
|
||||||
if (totalCost != other.totalCost) return false
|
if (totalCost != other.totalCost) return false
|
||||||
if (!alignMatrix.contentDeepEquals(other.alignMatrix)) return false
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = totalCost.hashCode()
|
|
||||||
result = 31 * result + alignMatrix.contentDeepHashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,25 +42,20 @@ public data class DynamicTimeWarpingData(val totalCost : Double = 0.0,
|
|||||||
* There is special cases for i = 0 or j = 0.
|
* There is special cases for i = 0 or j = 0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public fun costMatrix(series1 : DoubleBuffer, series2 : DoubleBuffer) : Array<DoubleArray> {
|
public fun costMatrix(series1 : DoubleBuffer, series2 : DoubleBuffer) : DoubleBufferND {
|
||||||
val dtwMatrix: Array<DoubleArray> = Array(series1.size){ row ->
|
val dtwMatrix = DoubleField.ndAlgebra.structureND(ShapeND(series1.size, series2.size)) {
|
||||||
DoubleArray(series2.size) { col ->
|
(row, col) -> abs(series1[row] - series2[col])
|
||||||
abs(series1[row] - series2[col])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for ( (row, col) in dtwMatrix.indices) {
|
||||||
for (i in dtwMatrix.indices) {
|
dtwMatrix[row, col] += when {
|
||||||
for (j in dtwMatrix[i].indices) {
|
row == 0 && col == 0 -> 0.0
|
||||||
dtwMatrix[i][j] += when {
|
row == 0 -> dtwMatrix[row, col - 1]
|
||||||
i == 0 && j == 0 -> 0.0
|
col == 0 -> dtwMatrix[row - 1, col]
|
||||||
i == 0 -> dtwMatrix[i][j-1]
|
else -> minOf(
|
||||||
j == 0 -> dtwMatrix[i-1][j]
|
dtwMatrix[row, col - 1],
|
||||||
else -> minOf(
|
dtwMatrix[row - 1, col],
|
||||||
dtwMatrix[i][j-1],
|
dtwMatrix[row - 1, col - 1]
|
||||||
dtwMatrix[i-1][j],
|
)
|
||||||
dtwMatrix[i-1][j-1]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dtwMatrix
|
return dtwMatrix
|
||||||
@ -95,27 +90,27 @@ public fun dynamicTimeWarping(series1 : DoubleBuffer, series2 : DoubleBuffer) :
|
|||||||
var cost = 0.0
|
var cost = 0.0
|
||||||
var pathLength = 0
|
var pathLength = 0
|
||||||
val costMatrix = costMatrix(series1, series2)
|
val costMatrix = costMatrix(series1, series2)
|
||||||
val alignMatrix : Array<BooleanArray> = Array(costMatrix.size) { BooleanArray(costMatrix.first().size) }
|
val alignMatrix: IntBufferND = IntRingOpsND.structureND(ShapeND(series1.size, series2.size)) {(row, col) -> 0}
|
||||||
val indexes = PathIndices(alignMatrix.lastIndex, alignMatrix.last().lastIndex)
|
val indexes = PathIndices(alignMatrix.indices.shape.first() - 1, alignMatrix.indices.shape.last() - 1)
|
||||||
|
|
||||||
with(indexes) {
|
with(indexes) {
|
||||||
alignMatrix[id_x][id_y] = true
|
alignMatrix[id_x, id_y] = 1
|
||||||
cost += costMatrix[id_x][id_y]
|
cost += costMatrix[id_x, id_y]
|
||||||
pathLength++
|
pathLength++
|
||||||
while (id_x != 0 || id_y != 0) {
|
while (id_x != 0 || id_y != 0) {
|
||||||
when {
|
when {
|
||||||
id_x == 0 || costMatrix[id_x][id_y] == costMatrix[id_x][id_y - 1] + abs(series1[id_x] - series2[id_y]) -> {
|
id_x == 0 || costMatrix[id_x, id_y] == costMatrix[id_x, id_y - 1] + abs(series1[id_x] - series2[id_y]) -> {
|
||||||
moveOption(LEFT_OFFSET)
|
moveOption(LEFT_OFFSET)
|
||||||
}
|
}
|
||||||
id_y == 0 || costMatrix[id_x][id_y] == costMatrix[id_x - 1][id_y] + abs(series1[id_x] - series2[id_y]) -> {
|
id_y == 0 || costMatrix[id_x, id_y] == costMatrix[id_x - 1, id_y] + abs(series1[id_x] - series2[id_y]) -> {
|
||||||
moveOption(BOTTOM_OFFSET)
|
moveOption(BOTTOM_OFFSET)
|
||||||
}
|
}
|
||||||
costMatrix[id_x][id_y] == costMatrix[id_x - 1][id_y - 1] + abs(series1[id_x] - series2[id_y]) -> {
|
costMatrix[id_x, id_y] == costMatrix[id_x - 1, id_y - 1] + abs(series1[id_x] - series2[id_y]) -> {
|
||||||
moveOption(DIAGONAL_OFFSET)
|
moveOption(DIAGONAL_OFFSET)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
alignMatrix[id_x][id_y] = true
|
alignMatrix[id_x, id_y] = 1
|
||||||
cost += costMatrix[id_x][id_y]
|
cost += costMatrix[id_x, id_y]
|
||||||
pathLength++
|
pathLength++
|
||||||
}
|
}
|
||||||
cost /= pathLength
|
cost /= pathLength
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2023 KMath contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.kscience.kmath.series
|
||||||
|
|
||||||
|
import space.kscience.kmath.nd.*
|
||||||
|
import space.kscience.kmath.operations.DoubleField
|
||||||
|
import space.kscience.kmath.operations.algebra
|
||||||
|
import space.kscience.kmath.operations.bufferAlgebra
|
||||||
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
|
import space.kscience.kmath.structures.asBuffer
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class DTWTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun someData() : Unit {
|
||||||
|
with(Double.algebra.bufferAlgebra) {
|
||||||
|
val firstSequence: DoubleArray = doubleArrayOf(0.0, 2.0, 3.0, 1.0, 3.0, 0.1, 0.0, 1.0)
|
||||||
|
val secondSequence: DoubleArray = doubleArrayOf(1.0, 0.0, 3.0, 0.0, 0.0, 3.0, 2.0, 0.0, 2.0)
|
||||||
|
|
||||||
|
val seriesOne: DoubleBuffer = firstSequence.asBuffer()
|
||||||
|
val seriesTwo: DoubleBuffer = secondSequence.asBuffer()
|
||||||
|
|
||||||
|
val result = dynamicTimeWarping(seriesOne, seriesTwo)
|
||||||
|
println("Total penalty coefficient: ${result.totalCost}")
|
||||||
|
print("Alignment: ")
|
||||||
|
println(result.alignMatrix)
|
||||||
|
for ((i , j) in result.alignMatrix.indices) {
|
||||||
|
if (result.alignMatrix[i, j] == 1) {
|
||||||
|
print("[$i, $j] ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user