如何在Spark 2.2中用Scala高效生成1-5元语法输出列?
嘿,我明白你的困扰了!Spark的NGram transformer确实只能一次处理单个N值,没法直接传1:5这种范围参数。不过有两种高效的方法可以实现你要的1到5元语法合并输出,不用反复创建新DataFrame,我给你详细讲讲:
方案一:自定义UDF(简洁灵活)
如果你的输入列已经是分词后的字符串数组(比如用Tokenizer处理过的列),可以写一个自定义UDF直接生成所有N范围的ngrams,一步到位:
import org.apache.spark.sql.functions.{udf, col} // 定义生成多N元语法的函数 def generateAllNgrams(tokens: Seq[String], minN: Int, maxN: Int): Seq[String] = { (minN to maxN).flatMap(n => { // sliding(n)生成窗口为n的滑动序列,再拼接成字符串 tokens.sliding(n).map(_.mkString(" ")) }) } // 注册UDF,指定输入为字符串数组,输出为字符串数组 val allNgramsUdf = udf((tokens: Seq[String]) => generateAllNgrams(tokens, 1, 5)) // 应用到你的DataFrame上 // 假设你的输入列是分词后的"tokens"列,输出列叫"all_ngrams" val resultDF = yourOriginalDF.withColumn("all_ngrams", allNgramsUdf(col("tokens")))
如果你的原始输入列是未分词的字符串,记得先加一步分词:
import org.apache.spark.ml.feature.Tokenizer val tokenizer = new Tokenizer() .setInputCol("incol") .setOutputCol("tokens") val tokenizedDF = tokenizer.transform(yourOriginalDF) // 再用上面的UDF处理tokenizedDF即可
方案二:用Pipeline批量生成后合并列(Spark优化友好)
如果你更倾向于用Spark原生的ML组件,避免自定义UDF,可以用Pipeline一次性生成所有N值的ngram列,再合并成一个列:
import org.apache.spark.ml.feature.NGram import org.apache.spark.ml.Pipeline import org.apache.spark.sql.functions.array_concat // 为1到5每个N值创建NGram transformer val ngramStages = (1 to 5).map(n => { new NGram() .setN(n) .setInputCol("tokens") // 同样假设是分词后的列 .setOutputCol(s"ngrams_$n") }).toArray // 创建Pipeline并执行 val pipeline = new Pipeline().setStages(ngramStages) val tempDF = pipeline.fit(tokenizedDF).transform(tokenizedDF) // 把所有单独的ngram列合并成一个列,再删除中间列 val resultDF = tempDF.withColumn( "all_ngrams", array_concat((1 to 5).map(n => col(s"ngrams_$n")): _*) ).drop((1 to 5).map(n => s"ngrams_$n"): _*)
两种方案对比
- UDF方案:代码更简洁,不需要生成中间列,适合中小数据量场景,逻辑直观易调试。
- Pipeline方案:利用Spark的ML优化引擎,适合大数据量场景,执行效率更高,而且可以和其他ML组件无缝集成。
根据你的数据规模和需求选就行啦!
内容的提问来源于stack exchange,提问作者schoon
相关产品推荐
相关产品推荐

