反复调用以降低内存中数据帧的速度

时间:2018-08-21 23:23:58

标签: r apache-spark apache-spark-ml sparklyr

假设我有40个连续(DoubleType)变量,我已使用ft_quantile_discretizer将其存储到四分位数中。识别所有变量的四分位数非常快,因为该函数支持一次执行多个变量。

接下来,我想对这些存储桶的变量进行热编码,但是目前不支持通过一次调用对所有这些变量进行热编码的功能。因此,通过循环遍历变量,我一次为每个存储桶变量分配了ft_string_indexerft_one_hot_encodersdf_separate_column。这样就完成了工作。但是,随着循环的进行,它会大大减慢速度。我以为它用完了内存,但无法弄清楚如何对其进行编程,以使其在变量之间以相同的速度执行。

如果q_vars是连续变量的变量名称字符数组(例如其中的40个),我该如何以更节省火花的方式编写代码?

for (v in q_vars) {
   data_sprk_q<-data_sprk_q %>% 
       ft_string_indexer(v,paste0(v,"b"),"keep",string_order_type = "alphabetAsc") %>%
       ft_one_hot_encoder(paste0(v,"b"),paste0(v,"bc")) %>%
       sdf_separate_column(paste0(v,"bc"),into=q_vars_cat_list[[v]]) 
}

我还尝试了使用所有引用的变量作为单个大型管道执行,但这也不能解决问题,因此我认为它与循环本身没有任何关系。

test_text<-paste0("data_sprk_q<-data_sprk_q %>% ", paste0("ft_string_indexer('",q_vars,"',paste0('",q_vars,"','b'),'keep',string_order_type = 'alphabetAsc') %>% ft_one_hot_encoder(paste0('",q_vars,"','b'),paste0('",q_vars,"','bc')) %>% sdf_separate_column(paste0('",q_vars,"','bc'),into=",q_vars_cat_list,")",collapse=" %>% "))
eval(parse(text=test_text))

任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:7)

通常,由于催化剂优化器的线性复杂度比线性复杂度差,因此预期使用长ML管道会出现一些(有时会相当大的)减速。除了将流程分为多个管道,以及中断两者之间的沿袭(使用检查点并将数据写入持久性存储并将其加载回),目前您无能为力。

但是,您当前的代码在此之上增加了许多问题:

  • 除非您使用10个以上的存储桶StringIndexer

    ft_string_indexer(v ,paste0(v, "b"), "keep", string_order_type = "alphabetAsc")
    

    只复制QuantileDiscretizer分配的标签。级别越多,按字典顺序使用时,行为就变得越不有用。

  • 可能根本不需要应用“一键编码”(在最坏的情况下可能是有害的),具体取决于下游过程,甚至对于线性模型,也可能并非必须如此(您可以认为分配的标签是有效的序号,并记录为标称值,并且增加尺寸是不希望的结果。)

  • 但是,最大的问题是sdf_separate_column的应用。

    • 通过增加表达式数量来增加执行计划的计算成本。
    • 通过将稀疏数据转换为密集数据来增加处理所需的内存量。
    • 内部sparklyr在每个索引上使用UserDefinedFunction,有效地导致了同一行的重新分配,解码和垃圾回收,这给群集带来了很大压力。
    • 最后但并非最不重要的一点是,它丢弃了Spark ML广泛使用的列元数据。

    我强烈建议您不要在此使用此功能。根据您的评论,您似乎希望在将结果传递给其他算法之前对列进行子集化-为此,您可以使用VectorSlicer

总体而言,您可以将管道重写为

set.seed(1)

df <- copy_to(sc, tibble(x=rnorm(100), y=runif(100), z=rpois(100, 1)))

input_cols <- colnames(df)
discretized_cols <- paste0(input_cols, "_d")
encoded_cols <- paste0(discretized_cols, "_e") %>% setNames(discretized_cols)

discretizer <- ft_quantile_discretizer(
  sc, input_cols = input_cols, output_cols = discretized_cols, num_buckets = 10
)
encoders <- lapply(
  discretized_cols, 
  function(x) ft_one_hot_encoder(sc, input_col=x, output_col=encoded_cols[x])
)

transformed_df <- do.call(ml_pipeline, c(list(discretizer), encoders)) %>%
  ml_fit(df) %>% 
  ml_transform(df)

并在需要时应用ft_vector_slicer。例如,要获取与x中的第一个,第三个和第六个存储桶相对应的值,您可以:

transformed_df %>% 
  ft_vector_slicer(
    input_col="x_d_e", output_col="x_d_e_s", indices=c(0, 2, 5))