Как преобразовать столбец фрейма данных Spark из массива [Int] в linalg.Vector?

У меня есть фрейм данных df, который выглядит так:

+--------+--------------------+
| user_id|        is_following|
+--------+--------------------+
|       1|[2, 3, 4, 5, 6, 7]  |
|       2|[20, 30, 40, 50]    |
+--------+--------------------+

Я могу подтвердить, что это схема:

root
 |-- user_id: integer (nullable = true)
 |-- is_following: array (nullable = true)
 |    |-- element: integer (containsNull = true)

Я хотел бы использовать процедуры машинного обучения Spark, такие как LDA, чтобы провести машинное обучение по этому поводу, требуя от меня преобразования столбца is_following в linalg.Vector (не вектор Scala). Когда я пытаюсь сделать это через

import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.linalg.Vectors

val assembler = new VectorAssembler().setInputCols(Array("is_following")).setOutputCol("features")
val output = assembler.transform(df)

Затем я получаю следующую ошибку:

java.lang.IllegalArgumentException: Data type ArrayType(IntegerType,true) is not supported.

Если я интерпретирую это правильно, я убираю из этого, что мне нужно преобразовать здесь типы из целых чисел в что-то еще. (Двойная? Строка?)

У меня вопрос: как лучше всего преобразовать этот массив во что-то, что будет правильно векторизоваться для конвейера ML?

РЕДАКТИРОВАТЬ: Если это поможет, мне не нужно таким образом структурировать фрейм данных. Я мог бы вместо этого сделать это:

+--------+------------+
| user_id|is_following|
+--------+------------+
|       1|           2|
|       1|           3|
|       1|           4|
|       1|           5|
|       1|           6|
|       1|           7|
|       2|          20|
|     ...|         ...|
+--------+------------+

person CJ Sullivan    schedule 17.10.2017    source источник
comment
Вы пытались сопоставить с Double и начать все сначала?   -  person Jacek Laskowski    schedule 17.10.2017
comment
Мне удалось восстановить исходную таблицу с двойными вместо целых чисел. Оттуда я попытался повторно преобразовать данные с помощью VectorAssembler, но получил аналогичную ошибку: java.lang.IllegalArgumentException: Data type ArrayType(DoubleType,true) is not supported. Я также могу преобразовать столбец is_following в удвоение из отредактированного фрейма данных (то есть с несколькими идентичными user_id строками), но это не совсем то, что Я хочу, так как мне нужно передавать массив значений, а не одно значение за раз.   -  person CJ Sullivan    schedule 18.10.2017


Ответы (2)


Таким образом, ваш исходный ввод может быть более подходящим, чем ваш преобразованный ввод. VectorAssembler от Spark требует, чтобы все столбцы были двойными, а не массивами двойников. Поскольку разные пользователи могут следить за разным количеством людей, ваша текущая структура может быть хорошей, вам просто нужно преобразовать is_following в Double, вы можете фактически сделать это с помощью Spark VectorIndexer https://spark.apache.org/docs/2.1.0/ml-features.html#vectorindexer или просто вручную сделайте это в SQL.

Итак, tl; dr - ошибка типа связана с тем, что Spark Vector поддерживает только Doubles (это, вероятно, изменится для данных изображений в не столь отдаленном будущем, но в любом случае не подходит для вашего варианта использования), и вы альтернативная структура на самом деле может подойти лучше (без группировки).

Вы можете найти пример совместной фильтрации в документации Spark, который пригодится вам в дальнейшем приключении - https://spark.apache.org/docs/latest/ml-collaborative-filtering.html. Удачи и получайте удовольствие от Spark ML :)

редактировать:

Я заметил, что вы сказали, что хотите использовать LDA для входных данных, поэтому давайте также посмотрим, как подготовить данные для этого формата. Для ввода LDA вы можете рассмотреть возможность использования CountVectorizer (см. https://spark.apache.org/docs/2.1.0/ml-features.html#countvectorizer)

person Holden    schedule 17.10.2017
comment
Спасибо за советы! Я легко могу просто использовать SQL, чтобы получить is_following в Double. Но я сбит с толку, потому что я надеялся воспользоваться преимуществами потенциальных улучшений скорости при обнаружении кластера на графике с помощью LDA (который через EM должен быть основан на графике). Если я конвертирую это в ArrayType (StringType) для CountVectorizer, потеряю ли я какое-либо преимущество, которое у меня было от того, что это графические данные, а не вектор текста? - person CJ Sullivan; 18.10.2017
comment
Хорошо ... после запуска этого (и он отлично прошел через LDA), я обнаружил, что сомневаюсь в моем понимании CountVectorizer. Неужели LDA не должно заботиться о том, что он получает, если это вектор строк (в данном случае эти строки на самом деле являются целыми числами, но это не имеет значения)? Таким образом, LDA по-прежнему будет обрабатывать данные в виде графика под капотом, и я не должен ничего скрывать с точки зрения скорости в долгосрочной перспективе, верно? - person CJ Sullivan; 18.10.2017

Простым решением как преобразования массива в linalg.Vector, так и одновременного преобразования целых чисел в двойные числа было бы использование UDF.

Используя ваш фрейм данных:

val spark = SparkSession.builder.getOrCreate()
import spark.implicits._

val df = spark.createDataFrame(Seq((1, Array(2,3,4,5,6,7)), (2, Array(20,30,40,50))))
  .toDF("user_id", "is_following")

val convertToVector = udf((array: Seq[Int]) => {
  Vectors.dense(array.map(_.toDouble).toArray)
})

val df2 = df.withColumn("is_following", convertToVector($"is_following"))

spark.implicits._ импортируется сюда, чтобы можно было использовать $, col() или '.

Печать фрейма данных df2 даст желаемый результат:

+-------+-------------------------+
|user_id|is_following             |
+-------+-------------------------+
|1      |[2.0,3.0,4.0,5.0,6.0,7.0]|
|2      |[20.0,30.0,40.0,50.0]    |
+-------+-------------------------+

схема:

root
 |-- user_id: integer (nullable = false)
 |-- is_following: vector (nullable = true)
person Shaido    schedule 18.10.2017
comment
Я сделал именно это и получил следующую ошибку: java.lang.IndexOutOfBoundsException: (4,0) not in [-4,4) x [-10,10). На всякий случай я переименовал user_id в label и is_following в features, но безрезультатно. - person CJ Sullivan; 18.10.2017
comment
@CJSullivan Хм, странно. Вы можете подтвердить свой вклад? В приведенном выше коде я создаю фрейм данных, который должен выглядеть точно так же, как в вопросе. Есть ли разница между этим и тем, что вы используете? - person Shaido; 18.10.2017
comment
@CJSullivan Это может иметь какое-то отношение к разной длине векторов, если вы пытаетесь применить к нему какое-то машинное обучение. Все векторы признаков должны быть одинаковой длины. - person Shaido; 18.10.2017
comment
Ага. Вот подсказка. Здесь все функции не могут быть одинаковой длины, потому что они представляют собой социальную диаграмму, и разные пользователи могут следить за разным количеством людей. - person CJ Sullivan; 18.10.2017
comment
Хотя что интересно, я запустил тот же тест DF сверху через LDA и ошибок не было. Схема у меня идентична вашей. Однако, когда я запускаю полный фрейм данных (с идентичной схемой) через LDA, я получаю эту ошибку. - person CJ Sullivan; 18.10.2017