Примеры кода на Kotlin
Популярные библиотеки
- Kotlin-statistics[1] — библиотека с набором функций-расширений для работы с коллекциями, необходимыми в задачах статистики, такими как mode, median, range, variance, standardDeviation, geometricMean и др. Также, библиотека предоставляет расширения для трансформации коллекций, агрегации, сэмплинга данных. Есть реализации алгоритмов классификации, регрессии. Библиотека поддается расшерению API, за счет объявления своих расшерений, реализация которых может использовать Apache Math. Библиотека не содержит собственной реализации структур данных - все опреации производятся над стандартными интерфейсами (Sequence, Iterable и т.п.), функционал которых расширен благодаря механизму фнкций-расширения в Kotlin[2]. Нет встроенной поддержки визуализации данных, но можно использовать TornadoFX, работающий со стандартными коллекциями.
- KMath[3] — аналог numpy: поддержка алгебраических структур, массиво-подобных коллекций, гистограмм и т.д. На текущий момент 19.04.20 находится в разработке.
- SMILE[4] — JVM фреймворк, для которого помимо официального API[5] на Kotlin существуют расширения SMILE-NLP-KT[6]. Фреймворк используется для решения различных задач машинного обучения, таких как: классификация, регрессия, кластеризация, использование генетических алгоритмов, KNN, вывод отсутствующих значений набора данных, обработку естественных языков. Есть встроенная поддержка визуализации данных, средств для чтения и нормализации данных различных форматов (csv, apache arrow, json, jdbc).
Так как Kotlin интеропабилен с Java, то помимо поддерживающих для Kotlin библиотек можно использовать библиотеки для Java, например:
- Deeplearning4j[7] — DSL на Java для конфигурации глубоких нейронных сетей. Библиотека поддерживает распределенные вычисления, используя Apache Spark. Помимо реализаций алгоритмов машинного обучения, библиотека содержит классы для загрузки и нормализации данных.
Также есть возможность[8] работы с NumPy.
Примеры кода
Примеры кода написаны на kotlin 1.3.71 для JVM, с использованием kotlin-statistics
Gradle зависимость:
repositories { maven { url 'https://jitpack.io' } } dependencies { implementation 'com.github.thomasnield:kotlin-statistics:-SNAPSHOT' }
Линейная регрессия
Пример линейной регрессии c применением Kotlin-statistics:
fun main() { val r = sequenceOf( 1.0 to 3.0, 2.0 to 6.0, 3.0 to 9.0, 4.0 to 11.8 ).simpleRegression() println(r.slope) // 2.9400000000000004 println(r.meanSquareError) // 0.006000000000000227 println(r.predict(5.0)). // 14.8 }
Байесовская классификация
Основная статья: Байесовская классификация.
Пример классификации при помощи Наивного Байесовского Классификатора:
import org.nield.kotlinstatistics.toNaiveBayesClassifier class Email(val message: String, val isSpam: Boolean) fun main() { val emails = listOf( Email("Hey! If you really want to enlarge your ML scores click here", isSpam = true), Email("Earn 50 more points for ML just by visiting this site!", isSpam = true), Email("Still have F grade? Professional help with ML right here", isSpam = true), Email("Hey, I left my phone at home. Email me if you need anything.", isSpam = false), Email("Stay At Home: COVID-19 news", isSpam = false), Email("Please see attachment for notes on today's meeting.", isSpam = false), Email("JetBrains license certificate", isSpam = false), Email("Your Education Pack expires soon ", isSpam = false) ) val nbc = emails.toNaiveBayesClassifier( featuresSelector = { it.message.splitWords().toSet() }, categorySelector = { it.isSpam } ) val spamInput = "your grade is still so bad, but I can help you to get more scores".splitWords().toSet() require(nbc.predict(spamInput) == true) { spamInput } val legitInput = "Thank you for placing the order ".splitWords().toSet() require(nbc.predict(legitInput) == false) { legitInput } } fun String.splitWords(): Sequence<String> = this.split(Regex("\\s")) .asSequence() .map { it.replace(Regex("[^A-Za-z]"), "") } .map { it.toLowerCase() } .filter { it.isNotEmpty() }
Кластеризация
Основная статья: Кластеризация.
Пример кластеризации с DBSCAN:
import org.nield.kotlinstatistics.dbScanCluster import kotlin.math.pow import kotlin.math.sin inline fun <V> IntProgression.mapDouble(mapper: (Double) -> V) = this.map { mapper(it.toDouble()) } data class Point(val coordinates: Pair<Double, Double>, val cluster: Int) fun main() { val firstCluster = (1..100 step 1) .mapDouble { x -> Point(x to x / 2, cluster = 1) } val secondCluster = (1..80 step 3) .mapDouble { x -> Point(x to (x / 12).pow(2) + 20, cluster = 2) } val thirdCluster = (60..150 step 1) .mapDouble { x -> Point(x to 10 * sin(x / 5) + 15, cluster = 3) } val points = firstCluster + secondCluster + thirdCluster val clusters = points.dbScanCluster( xSelector = { (coords) -> coords.first }, ySelector = { (coords) -> coords.second }, maximumRadius = 5.0, minPoints = 1 ) val pointsWithMatchedClusters = clusters.withIndex() .flatMap { (clusterIdx, matched) -> matched.points.map { p -> p to clusterIdx + 1 } } require(clusters.size == 3) { clusters.size } val pointsWithMismatchedCluster = pointsWithMatchedClusters.filterNot { (p, cluster) -> cluster == p.cluster } require(pointsWithMismatchedCluster.isEmpty()) { pointsWithMatchedClusters } }
Пример работы с матрицами
Пример использования средств языка и методов стандартной библиотеки для работы с матрицами
typealias Vector = List<Double> typealias Matrix = List<Vector> class MatrixBuilder { private var matrixWidth: Int? = null private val _result: MutableList<Vector> = mutableListOf() val result: Matrix = _result operator fun invoke(vararg vector: Double) = addVector(vector.toList()) operator fun invoke(vararg vector: Number) = addVector(vector.map { it.toDouble() }) private fun addVector(vectorList: List<Double>) { _result.add(vectorList) if (matrixWidth != null) { require(vectorList.size == matrixWidth) { "Vector size must be the same among all builder invocations: $vectorList, $_result" } } else { matrixWidth = vectorList.size } } } fun matrix(builder: MatrixBuilder.() -> Unit): Matrix = MatrixBuilder().apply(builder).result fun main() { val multiplied = matrix { this(1, 2, 3, 4) this(1, 2, 3, 4) this(1, 2, 3, 4) } * matrix { this(5, 6) this(7, 8) this(9, 10) this(11, 12) } multiplied .transpose() .print() } fun Matrix.transpose(): Matrix = this.asSequence() .map { it.withIndex() } .flatten() .groupBy({ it.index }, { it.value }) .values .toList() operator fun Matrix.times(other: Matrix): Matrix { val (rows1, cols1) = this.size() val (_, cols2) = other.size() return (0 until rows1).map { i -> (0 until cols2).map { j -> (0 until cols1).fold(0.0) { s, k -> s + this[i][k] * other[k][j] } } } } fun Matrix.size(): Pair<Int, Int> = this.size to this.first().size fun Pair<Int, Int>.zeroMatrix(): Matrix = List(this.first) { List(this.second) { 0.0 } } fun Matrix.print() = println(this.joinToString(separator = "\n") { it.joinToString(separator = " ") }) fun List<Matrix>.sum(): Matrix { val n = this.size val (rowsCount, colCount) = this[0].size() return (0 until rowsCount).map { i -> (0 until colCount).map { j -> (0 until n).fold(0.0) { s, k -> s + this[k][i][j] } } } }