Искра MLLib Kmeans из фрейма данных и обратно

Я стремлюсь применить алгоритм кластеризации kmeans к очень большому набору данных с помощью Spark (1.3.1) MLLib. Я вызвал данные из HDFS, используя hiveContext из Spark, и в конечном итоге хотел бы вернуть его туда таким образом - в этом формате

    |I.D     |cluster |
    ===================
    |546     |2       |
    |6534    |4       |
    |236     |5       |
    |875     |2       |

я запустил следующий код, где "данные" - это фрейм данных двойников и идентификатор для первого столбца.

    val parsedData = data.rdd.map(s => Vectors.dense(s.getDouble(1),s.getDouble(2))).cache()
    val clusters = KMeans.train(parsedData, 3, 20)

это работает успешно, я застрял теперь отображение кластеров обратно в их соответствующие идентификаторы, в фрейм данных, как описано выше. Я могу преобразовать его в datframe с помощью:

    sc.makeRDD(clusters.predict(parsedData).toArray()).toDF()

но это все, что у меня есть. этот пост находится на правильном пути, и этот пост Я думаю, что задает аналогичный вопрос моему.

Я подозреваю, что библиотека labeledPoint необходима. Любые комментарии, ответы будут оценены, ура.

Edit: только что найдено этой в списке пользователей Spark выглядит многообещающе

4 ответов


Я понимаю, что вы хотите получить DataFrame в конце. Я вижу два возможных решения. Я бы сказал, что выбор между ними-дело вкуса.

создать столбец из RDD

очень легко получить пары идентификаторов и кластеров в виде RDD:

val idPointRDD = data.rdd.map(s => (s.getInt(0), Vectors.dense(s.getDouble(1),s.getDouble(2)))).cache()
val clusters = KMeans.train(idPointRDD.map(_._2), 3, 20)
val clustersRDD = clusters.predict(idPointRDD.map(_._2))
val idClusterRDD = idPointRDD.map(_._1).zip(clustersRDD)

затем вы создаете фрейм данных из этого

val idCluster = idClusterRDD.toDF("id", "cluster")

он работает, потому что карта не изменяет порядок данных в RDD, поэтому вы можете просто zip ids с результатами предсказание.

используйте UDF (пользовательская функция)

Второй метод включает в себя использование clusters.predict метод как UDF:

val bcClusters = sc.broadcast(clusters)
def predict(x: Double, y: Double): Int = {
    bcClusters.value.predict(Vectors.dense(x, y))
}
sqlContext.udf.register("predict", predict _)

теперь мы можем использовать его для добавления предсказаний к данным:

val idCluster = data.selectExpr("id", "predict(x, y) as cluster")

имейте в виду, что Spark API не позволяет отменить регистрацию UDF. Это означает, что данные закрытия будут храниться в памяти.

неправильные / неоптимальные решения

  • использование кластеров.предсказать без вещание

он не будет работать в распределенной конфигурации. Edit: на самом деле это будет работать, я был смущен реализация прогноза для RDD, который использует широковещательные.

  • sc.makeRDD(clusters.predict(parsedData).toArray()).toDF()

toArray собирает все данные в драйвере. Это означает, что в распределенном режиме вы будете копировать идентификаторы кластера в один узел.


Я делаю что-то подобное, используя pySpark. Я предполагаю, что вы можете напрямую перевести это в Scala, поскольку нет ничего конкретного python. myPointsWithID-это мой RDD с идентификатором для каждой точки и точкой, представленной в виде массива значений.

# Get an RDD of only the vectors representing the points to be clustered
points = myPointsWithID.map(lambda (id, point): point)
clusters = KMeans.train(points, 
                        100, 
                        maxIterations=100, 
                        runs=50,
                        initializationMode='random')

# For each point in the original RDD, replace the point with the
# ID of the cluster the point belongs to. 
clustersBC = sc.broadcast(clusters)
pointClusters = myPointsWithID.map(lambda (id, point): (id, clustersBC.value.predict(point)))

из вашего кода, я предполагаю:

  • data - Это фрейм данных с тремя столбцами (label: Double, x1: Double и x2: Double)
  • вы хотите KMeans.predict использовать x1 и x2 для того, чтобы сделать назначение кластера closestCluster: Int
  • фрейм данных результата должен иметь форму (label: Double, closestCluster: Int)

вот простой пример приложения с некоторыми игрушечными данными, придерживающимися предполагаемой схемы:

import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.clustering.KMeans
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.sql.functions.{col, udf}

case class DataRow(label: Double, x1: Double, x2: Double)
val data = sqlContext.createDataFrame(sc.parallelize(Seq(
    DataRow(3, 1, 2),
    DataRow(5, 3, 4),
    DataRow(7, 5, 6),
    DataRow(6, 0, 0)
)))

val parsedData = data.rdd.map(s => Vectors.dense(s.getDouble(1),s.getDouble(2))).cache()
val clusters = KMeans.train(parsedData, 3, 20)
val t = udf { (x1: Double, x2: Double) => clusters.predict(Vectors.dense(x1, x2)) }
val result = data.select(col("label"), t(col("x1"), col("x2")))

в важной частью являются последние две строки.

  1. создает UDF (пользовательская функция), которая может быть непосредственно применена к столбцам фрейма данных (в этом случае два столбца x1 и x2).

  2. выбирает label столбец вместе с UDF применяется к x1 и x2 столбцы. Поскольку ОДС предскажет closestCluster, после этого result будет фреймом данных, состоящим из (label, closestCluster)


Дайте мне знать, если этот код работает для вас:

import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.clustering._

val rows = data.rdd.map(r => (r.getDouble(1),r.getDouble(2))).cache()
val vectors = rows.map(r => Vectors.dense(r._1, r._2))
val kMeansModel = KMeans.train(vectors, 3, 20)
val predictions = rows.map{r => (r._1, kMeansModel.predict(Vectors.dense(r._1, r._2)))}
val df = predictions.toDF("id", "cluster")
df.show