Pipeline详解及Spark MLlib使用示例(Scala/Java/Python)

     本文中,我们介绍机器学习管道的概念。机器学习管道提供一系列基于数据框的高级的接口来帮助用户建立和调试实际的机器学习管道。

管道里的主要概念

       MLlib提供标准的接口来使联合多个算法到单个的管道或者工作流,管道的概念源于scikit-learn项目。

       1.数据框:机器学习接口使用来自Spark SQL的数据框形式数据作为数据集,它可以处理多种数据类型。比如,一个数据框可以有不同的列存储文本、特征向量、标签值和预测值。

       2.转换器:转换器是将一个数据框变为另一个数据框的算法。比如,一个机器学习模型就是一个转换器,它将带有特征数据框转为预测值数据框。

       3.估计器:估计器是拟合一个数据框来产生转换器的算法。比如,一个机器学习算法就是一个估计器,它训练一个数据框产生一个模型。

       4.管道:一个管道串起多个转换器和估计器,明确一个机器学习工作流。

       5.参数:管道中的所有转换器和估计器使用共同的接口来指定参数。

数据框

       机器学习算法可以应用于多种类型的数据,如向量、文本、图像和结构化数据。管道接口中采用来自Spark SQL的数据框来支持多种类型的数据。可以查看Spark SQLdatatype reference来了解数据框支持的基础和结构化数据类型。除了Spark SQL指南中提到的数据类型外,数据框还可以使用机器学习向量类型。可以显式地建立数据框或者隐式地从规则的RDD建立数据框,下面的代码将会给出示例。数据框中的列需要命名。代码中的示例使用如“text”,“features“和”label“的名字。

管道组件

转换器

       转换器包含特征变化和学习模型。技术上来说,转化器通过方法transform(),在原始数据上增加一列或者多列来将一个数据框转为另一个数据框。如:

       1.一个特征转换器输入一个数据框,读取一个文本列,将其映射为新的特征向量列。输出一个新的带有特征向量列的数据框。

       2.一个学习模型转换器输入一个数据框,读取包括特征向量的列,预测每一个特征向量的标签。输出一个新的带有预测标签列的数据框。

估计器

       估计器指用来拟合或者训练数据的学习算法或者任何算法。技术上说,估计器通过fit()方法,接受一个数据框产生一个模型。比如,逻辑回归就是一个估计器,通过fit()来产生一个逻辑回归模型。

管道组件的特性

       转换器的transform()方法和估计器的fit()方法都是无状态性的。将来,有状态性的算法可能通过其他概念得到支持。

       每个转换器或估计器实例有唯一的编号,这个特征在制定参数的时候非常有用。

管道

       在机器学习中,运行一系列算法来处理和学习数据是非常常见的。如一个文档数据的处理工作流可能包括下列步骤:

       1.将文档氛围单个词语。

       2.将每个文档中的词语转为数字化的特征向量。

       3.使用特征向量和标签学习一个预测模型。

       MLlib将上述的工作流描述为管道,它包含一系列需要被执行的有顺序的管道阶段(转换器和估计器)。本节中我们将会使用上述文档处理工作流作为例子。

工作原理

       管道由一系列有顺序的阶段指定,每个状态时转换器或估计器。每个状态的运行是有顺序的,输入的数据框通过每个阶段进行改变。在转换器阶段,transform()方法被调用于数据框上。对于估计器阶段,fit()方法被调用来产生一个转换器,然后该转换器的transform()方法被调用在数据框上。

       下面的图说明简单的文档处理工作流的运行。

Pipeline详解及Spark MLlib使用示例(Scala/Java/Python)

       上面的图示中,第一行代表管道处理的三个阶段。第一二个蓝色的阶段是转换器,第三个红色框中的逻辑回归是估计器。底下一行代表管道中的数据流,圆筒指数据框。管道的fit()方法被调用于原始的数据框中,里面包含原始的文档和标签。分词器的transform()方法将原始文档分为词语,添加新的词语列到数据框中。哈希处理的transform()方法将词语列转换为特征向量,添加新的向量列到数据框中。然后,因为逻辑回归是估计器,管道先调用逻辑回归的fit()方法来产生逻辑回归模型。如果管道还有其它更多阶段,在将数据框传入下一个阶段之前,管道会先调用逻辑回归模型的transform()方法。

       整个管道是一个估计器。所以当管道的fit()方法运行后,会产生一个管道模型,管道模型是转换器。管道模型会在测试时被调用,下面的图示说明用法。

Pipeline详解及Spark MLlib使用示例(Scala/Java/Python)

       上面的图示中,管道模型和原始管道有同样数目的阶段,然而原始管道中的估计器此时变为了转换器。当管道模型的transform()方法被调用于测试数据集时,数据依次经过管道的各个阶段。每个阶段的transform()方法更新数据集,并将之传到下个阶段。

       管道和管道模型有助于确认训练数据和测试数据经过同样的特征处理流程。

详细信息

       DAG管道:管道的状态是有序的队列。这儿给的例子都是线性的管道,也就是说管道的每个阶段使用上一个阶段产生的数据。我们也可以产生非线性的管道,数据流向为无向非环图(DAG)。这种图通常需要明确地指定每个阶段的输入和输出列名(通常以指定参数的形式)。如果管道是DAG形式,则每个阶段必须以拓扑序的形式指定。

       运行时间检查:因为管道可以运行在多种数据类型上,所以不能使用编译时间检查。管道和管道模型在实际运行管道之前就会进行运行时间检查。这种检查通过数据框摘要,它描述了数据框中各列的类型。

      管道的唯一阶段:管道的的每个阶段需要是唯一的实体。如同样的实体“哈希变换”不可以进入管道两次,因为管道的每个阶段必须有唯一的ID。当然“哈希变换1”和“哈希变换2”(都是哈希变换类型)可以进入同个管道两次,因为他们有不同的ID。

参数

      MLlib估计器和转换器使用统一的接口来指定参数。Param是有完备文档的已命名参数。ParamMap是一些列“参数-值”对。

      有两种主要的方法来向算法传递参数:

      1.给实体设置参数。比如,lr是一个逻辑回归实体,通过lr.setMaxIter(10)来使得lr在拟合的时候最多迭代10次。这个接口与spark.mllib包相似。

2.传递ParamMap到fit()或者transform()。所有在ParamMap里的参数都将通过设置被重写。

      参数属于指定估计器和转换器实体过程。因此,如果我们有两个逻辑回归实体lr1和lr2,我们可以建立一个ParamMap来指定两个实体的最大迭代次数参数:ParamMap(lr1.maxIter -> 10, lr2.maxIter -> 20)。这在一个管道里有两个算法都有最大迭代次数参数时非常有用。

存储和读取管道

      我们经常需要将管道存储到磁盘以供下次使用。在Spark1.6中,模型导入导出功能新添了管道接口,支持大多数转换器。请到算法接口文档查看是否支持存储和读入。

代码示例

      下面给出上述讨论功能的代码示例:

估计器、转换器和Param示例:

Scala:

[plain] view plain copy
  1. import org.apache.spark.ml.classification.LogisticRegression  
  2. import org.apache.spark.ml.linalg.{Vector, Vectors}  
  3. import org.apache.spark.ml.param.ParamMap  
  4. import org.apache.spark.sql.Row  
  5.   
  6. // Prepare training data from a list of (label, features) tuples.  
  7. val training = spark.createDataFrame(Seq(  
  8.   (1.0, Vectors.dense(0.0, 1.1, 0.1)),  
  9.   (0.0, Vectors.dense(2.0, 1.0, -1.0)),  
  10.   (0.0, Vectors.dense(2.0, 1.3, 1.0)),  
  11.   (1.0, Vectors.dense(0.0, 1.2, -0.5))  
  12. )).toDF("label", "features")  
  13.   
  14. // Create a LogisticRegression instance. This instance is an Estimator.  
  15. val lr = new LogisticRegression()  
  16. // Print out the parameters, documentation, and any default values.  
  17. println("LogisticRegression parameters:\n" + lr.explainParams() + "\n")  
  18.   
  19. // We may set parameters using setter methods.  
  20. lr.setMaxIter(10)  
  21.   .setRegParam(0.01)  
  22.   
  23. // Learn a LogisticRegression model. This uses the parameters stored in lr.  
  24. val model1 = lr.fit(training)  
  25. // Since model1 is a Model (i.e., a Transformer produced by an Estimator),  
  26. // we can view the parameters it used during fit().  
  27. // This prints the parameter (name: value) pairs, where names are unique IDs for this  
  28. // LogisticRegression instance.  
  29. println("Model 1 was fit using parameters: " + model1.parent.extractParamMap)  
  30.   
  31. // We may alternatively specify parameters using a ParamMap,  
  32. // which supports several methods for specifying parameters.  
  33. val paramMap = ParamMap(lr.maxIter -> 20)  
  34.   .put(lr.maxIter, 30)  // Specify 1 Param. This overwrites the original maxIter.  
  35.   .put(lr.regParam -> 0.1, lr.threshold -> 0.55)  // Specify multiple Params.  
  36.   
  37. // One can also combine ParamMaps.  
  38. val paramMap2 = ParamMap(lr.probabilityCol -> "myProbability")  // Change output column name.  
  39. val paramMapCombined = paramMap ++ paramMap2  
  40.   
  41. // Now learn a new model using the paramMapCombined parameters.  
  42. // paramMapCombined overrides all parameters set earlier via lr.set* methods.  
  43. val model2 = lr.fit(training, paramMapCombined)  
  44. println("Model 2 was fit using parameters: " + model2.parent.extractParamMap)  
  45.   
  46. // Prepare test data.  
  47. val test = spark.createDataFrame(Seq(  
  48.   (1.0, Vectors.dense(-1.0, 1.5, 1.3)),  
  49.   (0.0, Vectors.dense(3.0, 2.0, -0.1)),  
  50.   (1.0, Vectors.dense(0.0, 2.2, -1.5))  
  51. )).toDF("label", "features")  
  52.   
  53. // Make predictions on test data using the Transformer.transform() method.  
  54. // LogisticRegression.transform will only use the 'features' column.  
  55. // Note that model2.transform() outputs a 'myProbability' column instead of the usual  
  56. // 'probability' column since we renamed the lr.probabilityCol parameter previously.  
  57. model2.transform(test)  
  58.   .select("features", "label", "myProbability", "prediction")  
  59.   .collect()  
  60.   .foreach { case Row(features: Vector, label: Double, prob: Vector, prediction: Double) =>  
  61.     println(s"($features, $label) -> prob=$prob, prediction=$prediction")  
  62.   }  
Java:

[java] view plain copy
  1. import java.util.Arrays;  
  2. import java.util.List;  
  3.   
  4. import org.apache.spark.ml.classification.LogisticRegression;  
  5. import org.apache.spark.ml.classification.LogisticRegressionModel;  
  6. import org.apache.spark.ml.linalg.VectorUDT;  
  7. import org.apache.spark.ml.linalg.Vectors;  
  8. import org.apache.spark.ml.param.ParamMap;  
  9. import org.apache.spark.sql.Dataset;  
  10. import org.apache.spark.sql.Row;  
  11. import org.apache.spark.sql.RowFactory;  
  12. import org.apache.spark.sql.types.DataTypes;  
  13. import org.apache.spark.sql.types.Metadata;  
  14. import org.apache.spark.sql.types.StructField;  
  15. import org.apache.spark.sql.types.StructType;  
  16.   
  17. // Prepare training data.  
  18. List<Row> dataTraining = Arrays.asList(  
  19.     RowFactory.create(1.0, Vectors.dense(0.01.10.1)),  
  20.     RowFactory.create(0.0, Vectors.dense(2.01.0, -1.0)),  
  21.     RowFactory.create(0.0, Vectors.dense(2.01.31.0)),  
  22.     RowFactory.create(1.0, Vectors.dense(0.01.2, -0.5))  
  23. );  
  24. StructType schema = new StructType(new StructField[]{  
  25.     new StructField("label", DataTypes.DoubleType, false, Metadata.empty()),  
  26.     new StructField("features"new VectorUDT(), false, Metadata.empty())  
  27. });  
  28. Dataset<Row> training = spark.createDataFrame(dataTraining, schema);  
  29.   
  30. // Create a LogisticRegression instance. This instance is an Estimator.  
  31. LogisticRegression lr = new LogisticRegression();  
  32. // Print out the parameters, documentation, and any default values.  
  33. System.out.println("LogisticRegression parameters:\n" + lr.explainParams() + "\n");  
  34.   
  35. // We may set parameters using setter methods.  
  36. lr.setMaxIter(10).setRegParam(0.01);  
  37.   
  38. // Learn a LogisticRegression model. This uses the parameters stored in lr.  
  39. LogisticRegressionModel model1 = lr.fit(training);  
  40. // Since model1 is a Model (i.e., a Transformer produced by an Estimator),  
  41. // we can view the parameters it used during fit().  
  42. // This prints the parameter (name: value) pairs, where names are unique IDs for this  
  43. // LogisticRegression instance.  
  44. System.out.println("Model 1 was fit using parameters: " + model1.parent().extractParamMap());  
  45.   
  46. // We may alternatively specify parameters using a ParamMap.  
  47. ParamMap paramMap = new ParamMap()  
  48.   .put(lr.maxIter().w(20))  // Specify 1 Param.  
  49.   .put(lr.maxIter(), 30)  // This overwrites the original maxIter.  
  50.   .put(lr.regParam().w(0.1), lr.threshold().w(0.55));  // Specify multiple Params.  
  51.   
  52. // One can also combine ParamMaps.  
  53. ParamMap paramMap2 = new ParamMap()  
  54.   .put(lr.probabilityCol().w("myProbability"));  // Change output column name  
  55. ParamMap paramMapCombined = paramMap.$plus$plus(paramMap2);  
  56.   
  57. // Now learn a new model using the paramMapCombined parameters.  
  58. // paramMapCombined overrides all parameters set earlier via lr.set* methods.  
  59. LogisticRegressionModel model2 = lr.fit(training, paramMapCombined);  
  60. System.out.println("Model 2 was fit using parameters: " + model2.parent().extractParamMap());  
  61.   
  62. // Prepare test documents.  
  63. List<Row> dataTest = Arrays.asList(  
  64.     RowFactory.create(1.0, Vectors.dense(-1.01.51.3)),  
  65.     RowFactory.create(0.0, Vectors.dense(3.02.0, -0.1)),  
  66.     RowFactory.create(1.0, Vectors.dense(0.02.2, -1.5))  
  67. );  
  68. Dataset<Row> test = spark.createDataFrame(dataTest, schema);  
  69.   
  70. // Make predictions on test documents using the Transformer.transform() method.  
  71. // LogisticRegression.transform will only use the 'features' column.  
  72. // Note that model2.transform() outputs a 'myProbability' column instead of the usual  
  73. // 'probability' column since we renamed the lr.probabilityCol parameter previously.  
  74. Dataset<Row> results = model2.transform(test);  
  75. Dataset<Row> rows = results.select("features""label""myProbability""prediction");  
  76. for (Row r: rows.collectAsList()) {  
  77.   System.out.println("(" + r.get(0) + ", " + r.get(1) + ") -> prob=" + r.get(2)  
  78.     + ", prediction=" + r.get(3));  
  79. }  
Python:

[python] view plain copy
  1. from pyspark.ml.linalg import Vectors  
  2. from pyspark.ml.classification import LogisticRegression  
  3.   
  4. # Prepare training data from a list of (label, features) tuples.  
  5. training = spark.createDataFrame([  
  6.     (1.0, Vectors.dense([0.01.10.1])),  
  7.     (0.0, Vectors.dense([2.01.0, -1.0])),  
  8.     (0.0, Vectors.dense([2.01.31.0])),  
  9.     (1.0, Vectors.dense([0.01.2, -0.5]))], ["label""features"])  
  10.   
  11. # Create a LogisticRegression instance. This instance is an Estimator.  
  12. lr = LogisticRegression(maxIter=10, regParam=0.01)  
  13. # Print out the parameters, documentation, and any default values.  
  14. print "LogisticRegression parameters:\n" + lr.explainParams() + "\n"  
  15.   
  16. # Learn a LogisticRegression model. This uses the parameters stored in lr.  
  17. model1 = lr.fit(training)  
  18.   
  19. # Since model1 is a Model (i.e., a transformer produced by an Estimator),  
  20. # we can view the parameters it used during fit().  
  21. # This prints the parameter (name: value) pairs, where names are unique IDs for this  
  22. # LogisticRegression instance.  
  23. print "Model 1 was fit using parameters: "  
  24. print model1.extractParamMap()  
  25.   
  26. # We may alternatively specify parameters using a Python dictionary as a paramMap  
  27. paramMap = {lr.maxIter: 20}  
  28. paramMap[lr.maxIter] = 30  # Specify 1 Param, overwriting the original maxIter.  
  29. paramMap.update({lr.regParam: 0.1, lr.threshold: 0.55})  # Specify multiple Params.  
  30.   
  31. # You can combine paramMaps, which are python dictionaries.  
  32. paramMap2 = {lr.probabilityCol: "myProbability"}  # Change output column name  
  33. paramMapCombined = paramMap.copy()  
  34. paramMapCombined.update(paramMap2)  
  35.   
  36. # Now learn a new model using the paramMapCombined parameters.  
  37. # paramMapCombined overrides all parameters set earlier via lr.set* methods.  
  38. model2 = lr.fit(training, paramMapCombined)  
  39. print "Model 2 was fit using parameters: "  
  40. print model2.extractParamMap()  
  41.   
  42. # Prepare test data  
  43. test = spark.createDataFrame([  
  44.     (1.0, Vectors.dense([-1.01.51.3])),  
  45.     (0.0, Vectors.dense([3.02.0, -0.1])),  
  46.     (1.0, Vectors.dense([0.02.2, -1.5]))], ["label""features"])  
  47.   
  48. # Make predictions on test data using the Transformer.transform() method.  
  49. # LogisticRegression.transform will only use the 'features' column.  
  50. # Note that model2.transform() outputs a "myProbability" column instead of the usual  
  51. # 'probability' column since we renamed the lr.probabilityCol parameter previously.  
  52. prediction = model2.transform(test)  
  53. selected = prediction.select("features""label""myProbability""prediction")  
  54. for row in selected.collect():  
  55.     print row  

管道示例:

Scala:

[plain] view plain copy
  1. import org.apache.spark.ml.{Pipeline, PipelineModel}  
  2. import org.apache.spark.ml.classification.LogisticRegression  
  3. import org.apache.spark.ml.feature.{HashingTF, Tokenizer}  
  4. import org.apache.spark.ml.linalg.Vector  
  5. import org.apache.spark.sql.Row  
  6.   
  7. // Prepare training documents from a list of (id, text, label) tuples.  
  8. val training = spark.createDataFrame(Seq(  
  9.   (0L, "a b c d e spark", 1.0),  
  10.   (1L, "b d", 0.0),  
  11.   (2L, "spark f g h", 1.0),  
  12.   (3L, "hadoop mapreduce", 0.0)  
  13. )).toDF("id", "text", "label")  
  14.   
  15. // Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.  
  16. val tokenizer = new Tokenizer()  
  17.   .setInputCol("text")  
  18.   .setOutputCol("words")  
  19. val hashingTF = new HashingTF()  
  20.   .setNumFeatures(1000)  
  21.   .setInputCol(tokenizer.getOutputCol)  
  22.   .setOutputCol("features")  
  23. val lr = new LogisticRegression()  
  24.   .setMaxIter(10)  
  25.   .setRegParam(0.01)  
  26. val pipeline = new Pipeline()  
  27.   .setStages(Array(tokenizer, hashingTF, lr))  
  28.   
  29. // Fit the pipeline to training documents.  
  30. val model = pipeline.fit(training)  
  31.   
  32. // Now we can optionally save the fitted pipeline to disk  
  33. model.write.overwrite().save("/tmp/spark-logistic-regression-model")  
  34.   
  35. // We can also save this unfit pipeline to disk  
  36. pipeline.write.overwrite().save("/tmp/unfit-lr-model")  
  37.   
  38. // And load it back in during production  
  39. val sameModel = PipelineModel.load("/tmp/spark-logistic-regression-model")  
  40.   
  41. // Prepare test documents, which are unlabeled (id, text) tuples.  
  42. val test = spark.createDataFrame(Seq(  
  43.   (4L, "spark i j k"),  
  44.   (5L, "l m n"),  
  45.   (6L, "mapreduce spark"),  
  46.   (7L, "apache hadoop")  
  47. )).toDF("id", "text")  
  48.   
  49. // Make predictions on test documents.  
  50. model.transform(test)  
  51.   .select("id", "text", "probability", "prediction")  
  52.   .collect()  
  53.   .foreach { case Row(id: Long, text: String, prob: Vector, prediction: Double) =>  
  54.     println(s"($id, $text) --> prob=$prob, prediction=$prediction")  
  55.   }  
Java:

[java] view plain copy
  1. import java.util.Arrays;  
  2.   
  3. import org.apache.spark.ml.Pipeline;  
  4. import org.apache.spark.ml.PipelineModel;  
  5. import org.apache.spark.ml.PipelineStage;  
  6. import org.apache.spark.ml.classification.LogisticRegression;  
  7. import org.apache.spark.ml.feature.HashingTF;  
  8. import org.apache.spark.ml.feature.Tokenizer;  
  9. import org.apache.spark.sql.Dataset;  
  10. import org.apache.spark.sql.Row;  
  11.   
  12. // Prepare training documents, which are labeled.  
  13. Dataset<Row> training = spark.createDataFrame(Arrays.asList(  
  14.   new JavaLabeledDocument(0L, "a b c d e spark"1.0),  
  15.   new JavaLabeledDocument(1L, "b d"0.0),  
  16.   new JavaLabeledDocument(2L, "spark f g h"1.0),  
  17.   new JavaLabeledDocument(3L, "hadoop mapreduce"0.0)  
  18. ), JavaLabeledDocument.class);  
  19.   
  20. // Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.  
  21. Tokenizer tokenizer = new Tokenizer()  
  22.   .setInputCol("text")  
  23.   .setOutputCol("words");  
  24. HashingTF hashingTF = new HashingTF()  
  25.   .setNumFeatures(1000)  
  26.   .setInputCol(tokenizer.getOutputCol())  
  27.   .setOutputCol("features");  
  28. LogisticRegression lr = new LogisticRegression()  
  29.   .setMaxIter(10)  
  30.   .setRegParam(0.01);  
  31. Pipeline pipeline = new Pipeline()  
  32.   .setStages(new PipelineStage[] {tokenizer, hashingTF, lr});  
  33.   
  34. // Fit the pipeline to training documents.  
  35. PipelineModel model = pipeline.fit(training);  
  36.   
  37. // Prepare test documents, which are unlabeled.  
  38. Dataset<Row> test = spark.createDataFrame(Arrays.asList(  
  39.   new JavaDocument(4L, "spark i j k"),  
  40.   new JavaDocument(5L, "l m n"),  
  41.   new JavaDocument(6L, "mapreduce spark"),  
  42.   new JavaDocument(7L, "apache hadoop")  
  43. ), JavaDocument.class);  
  44.   
  45. // Make predictions on test documents.  
  46. Dataset<Row> predictions = model.transform(test);  
  47. for (Row r : predictions.select("id""text""probability""prediction").collectAsList()) {  
  48.   System.out.println("(" + r.get(0) + ", " + r.get(1) + ") --> prob=" + r.get(2)  
  49.     + ", prediction=" + r.get(3));  
  50. }  
Python:

[python] view plain copy
  1. from pyspark.ml import Pipeline  
  2. from pyspark.ml.classification import LogisticRegression  
  3. from pyspark.ml.feature import HashingTF, Tokenizer  
  4.   
  5. # Prepare training documents from a list of (id, text, label) tuples.  
  6. training = spark.createDataFrame([  
  7.     (0"a b c d e spark"1.0),  
  8.     (1"b d"0.0),  
  9.     (2"spark f g h"1.0),  
  10.     (3"hadoop mapreduce"0.0)], ["id""text""label"])  
  11.   
  12. # Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.  
  13. tokenizer = Tokenizer(inputCol="text", outputCol="words")  
  14. hashingTF = HashingTF(inputCol=tokenizer.getOutputCol(), outputCol="features")  
  15. lr = LogisticRegression(maxIter=10, regParam=0.01)  
  16. pipeline = Pipeline(stages=[tokenizer, hashingTF, lr])  
  17.   
  18. # Fit the pipeline to training documents.  
  19. model = pipeline.fit(training)  
  20.   
  21. # Prepare test documents, which are unlabeled (id, text) tuples.  
  22. test = spark.createDataFrame([  
  23.     (4"spark i j k"),  
  24.     (5"l m n"),  
  25.     (6"mapreduce spark"),  
  26.     (7"apache hadoop")], ["id""text"])  
  27.   
  28. # Make predictions on test documents and print columns of interest.  
  29. prediction = model.transform(test)  
  30. selected = prediction.select("id""text""prediction")  
  31. for row in selected.collect():  
  32.     print(row)  

文章出处:https://blog.****.net/liulingyuan6/article/details/53576550