【版權聲明】本文為原創,轉載請注明原地址 http://www.lxweimin.com/p/99ad946dbca1
同步更新在個人網站:http://www.wangpengcufe.com/machinelearning/ml-ml11/
一、概念
一個典型的機器學習過程從數據收集開始,要經歷多個步驟,才能得到需要的輸出。這非常類似于流水線式工作,即通常會包含源數據ETL(抽取、轉化、加載),數據預處理,指標提取,模型訓練與交叉驗證,新數據預測等步驟。
MLlib標準化了用于機器學習算法的API,從而使將多種算法組合到單個管道或工作流程中變得更加容易。 本節介紹了Pipelines API引入的關鍵概念,其中PipeLine(管道)概念主要受scikit-learn項目的啟發。
在介紹工作流之前,我們先來了解幾個重要概念:
DataFrame:使用Spark SQL中的DataFrame作為ML數據集,該數據集可以保存各種數據類型。 例如,DataFrame可以具有不同的列,用于存儲文本,特征向量,真實標簽和預測。
Transformer:翻譯成轉換器,是一種算法,可以將一個DataFrame轉換為另一個DataFrame。 例如,ML模型是一個Transformer,它將具有特征的DataFrame轉換為具有預測的DataFrame。
Estimator:翻譯成評估器,它是學習算法或在訓練數據上的訓練方法的概念抽象。在 Pipeline 里通常是被用來操作 DataFrame 數據并生產一個 Transformer。從技術上講,Estimator實現了一個方法fit(),它接受一個DataFrame并產生一個轉換器。例如,諸如LogisticRegression之類的學習算法是Estimator,調用fit()可以訓練LogisticRegressionModel,后者是Model,因此是Transformer。
Parameter:Parameter 被用來設置 Transformer 或者 Estimator 的參數。現在,所有轉換器和估計器可共享用于指定參數的公共API。ParamMap是一組(參數,值)對。
PipeLine:翻譯為工作流或者管道。管道將多個“變形器”和“估計器”鏈接在一起,以指定ML工作流程,并獲得結果輸出。 例如,簡單的文本文檔處理工作流程可能包括幾個階段:
1、將每個文檔的文本拆分為單詞。
2、將每個文檔的單詞轉換成數字特征向量。
3、使用特征向量和標簽學習預測模型。
MLlib將這樣的工作流表示為“管道”,它由要按特定順序運行的一系列PipelineStages(變壓器和估計器)組成。
二、工作原理
要構建一個 Pipeline工作流,首先需要定義 Pipeline 中的各個工作流階段PipelineStage,(包括轉換器和評估器),比如指標提取和轉換模型訓練等。有了這些處理特定問題的轉換器和 評估器,就可以按照具體的處理邏輯有序的組織PipelineStages 并創建一個Pipeline。比如:
Pipeline pipeline = new Pipeline().setStages(new PipelineStage[]{tokenizer,hashingTF,lr});
然后就可以把訓練數據集作為輸入參數,調用 Pipeline 實例的 fit 方法來開始以流的方式來處理源訓練數據。這個調用會返回一個 PipelineModel 類實例,進而被用來預測測試數據的標簽。更具體的說,工作流的各個階段按順序運行,輸入的DataFrame在它通過每個階段時被轉換。 對于Transformer階段,在DataFrame上調用transform()方法。 對于估計器階段,調用fit()方法來生成一個轉換器(它成為PipelineModel的一部分或擬合的Pipeline),并且在DataFrame上調用該轉換器的transform()方法。
上面,頂行表示具有三個階段的流水線。 前兩個(Tokenizer和HashingTF)是Transformers(藍色),第三個(LogisticRegression)是Estimator(紅色)。 底行表示流經管線的數據,其中圓柱表示DataFrames。 在原始DataFrame上調用Pipeline.fit()方法,它具有原始文本文檔和標簽。 Tokenizer.transform()方法將原始文本文檔拆分為單詞,向DataFrame添加一個帶有單詞的新列。 HashingTF.transform()方法將字列轉換為特征向量,向這些向量添加一個新列到DataFrame。 現在,由于LogisticRegression是一個Estimator,Pipeline首先調用LogisticRegression.fit()產生一個LogisticRegressionModel。 如果流水線有更多的階段,則在將DataFrame傳遞到下一個階段之前,將在DataFrame上調用LogisticRegressionModel的transform()方法。
值得注意的是,工作流本身也可以看做是一個估計器。在工作流的fit()方法運行之后,它產生一個PipelineModel,它是一個Transformer。 這個管道模型將在測試數據的時候使用。 下圖說明了這種用法。
在上圖中,PipelineModel具有與原始流水線相同的級數,但是原始流水線中的所有估計器都變為變換器。 當在測試數據集上調用PipelineModel的transform()方法時,數據按順序通過擬合的工作流。 每個階段的transform()方法更新數據集并將其傳遞到下一個階段。工作流和工作流模型有助于確保培訓和測試數據通過相同的特征處理步驟。
三、代碼實現
以邏輯斯蒂回歸為例,構建一個典型的機器學習過程,來具體介紹一下工作流是如何應用的。我們的目的是查找出所有包含”spark”的句子,即將包含”spark”的句子的標簽設為1,沒有”spark”的句子的標簽設為0。
3.1、構建訓練數據集
import java.util.Arrays;
import java.util.List;
import org.apache.spark.ml.Pipeline;
import org.apache.spark.ml.PipelineModel;
import org.apache.spark.ml.PipelineStage;
import org.apache.spark.ml.classification.LogisticRegression;
import org.apache.spark.ml.feature.HashingTF;
import org.apache.spark.ml.feature.Tokenizer;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.Metadata;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;
SparkSession spark = SparkSession.builder().appName("MLPipelines").master("local").getOrCreate();
//構建訓練數據集
List<Row> data = Arrays.asList(RowFactory.create(0L, "a b c d e spark", 1.0),
RowFactory.create(1L, "b d", 0.0),
RowFactory.create(2L, "spark f g h", 1.0),
RowFactory.create(3L, "hadoop mapreduce", 0.0));
System.out.println(data);
/**
*控制臺輸出結果:
-------------------------------------------------------------------------------------
[[0,a b c d e spark,1.0], [1,b d,0.0], [2,spark f g h,1.0], [3,hadoop mapreduce,0.0]]
-------------------------------------------------------------------------------------
**/
StructType schema = new StructType(new StructField[] {
new StructField("id",DataTypes.LongType,false,Metadata.empty()),
new StructField("text", DataTypes.StringType, false, Metadata.empty()),
new StructField("label", DataTypes.DoubleType, false, Metadata.empty()),
});
Dataset<Row> training = spark.createDataFrame(data,schema);
training.show(false);
/**
*控制臺輸出結果:
+---+----------------+-----+
|id |text |label|
+---+----------------+-----+
|0 |a b c d e spark |1.0 |
|1 |b d |0.0 |
|2 |spark f g h |1.0 |
|3 |hadoop mapreduce|0.0 |
+---+----------------+-----+
**/
3.2、定義 Pipeline 中的各個工作流階段PipelineStage
在這一步中我們要定義 Pipeline 中的各個工作流階段PipelineStage,包括轉換器和評估器,具體的,包含tokenizer, hashingTF和lr三個步驟。
Tokenizer tokenizer = new Tokenizer().setInputCol("text")
.setOutputCol("words");
HashingTF hashingTF = new HashingTF().setNumFeatures(1000)
.setInputCol(tokenizer.getOutputCol())
.setOutputCol("features");
LogisticRegression lr = new LogisticRegression().setMaxIter(10).setRegParam(0.01);
3.3、創建一個Pipeline
有了這些處理特定問題的轉換器和評估器,接下來就可以按照具體的處理邏輯有序的組織PipelineStages 并創建一個Pipeline。
Pipeline pipeline = new Pipeline().setStages(new PipelineStage[]{tokenizer,hashingTF,lr});
3.4、創建模型
現在構建的Pipeline本質上是一個Estimator,在它的fit()方法運行之后,它將產生一個PipelineModel,它是一個Transformer。
PipelineModel model = pipeline.fit(training);
我們可以看到,model的類型是一個PipelineModel,這個管道模型將在測試數據的時候使用。所以接下來,我們先構建測試數據。
List<Row> testRaw = Arrays.asList(RowFactory.create(4L, "spark i j k"),
RowFactory.create(5L, "l m n"),
RowFactory.create(6L, "spark a"),
RowFactory.create(7L, "apache hadoop")
);
Dataset<Row> test = spark.createDataFrame(testRaw,schema);
test.select("id", "text").show(false);
/**
*控制臺輸出結果:
+---+-------------+
|id |text |
+---+-------------+
|4 |spark i j k |
|5 |l m n |
|6 |spark a |
|7 |apache hadoop|
+---+-------------+
**/
3.5、預測結果
然后,我們調用我們訓練好的PipelineModel的transform()方法,讓測試數據按順序通過擬合的工作流,生成我們所需要的預測結果。
model.transform(test).select("id", "text", "probability", "prediction").show(false);
/**
*控制臺輸出結果:
+---+--------------+----------------------------------------+----------+
|id |text |probability |prediction|
+---+--------------+----------------------------------------+----------+
|4 |spark i j k |[0.540643354485232,0.45935664551476796] |0.0 |
|5 |l m n |[0.9334382627383527,0.06656173726164716]|0.0 |
|6 |spark a |[0.1504143004807332,0.8495856995192668] |1.0 |
|7 |apache hadoop|[0.9768636139518375,0.02313638604816238]|0.0 |
+---+--------------+----------------------------------------+----------+
**/
通過上述結果,我們可以看到,第4句和第6句中都包含”spark”,其中第六句的預測是1,與我們希望的相符;而第4句雖然預測的依然是0,但是通過概率我們可以看到,第4句有46%的概率預測是1,而第5句、第7句分別只有7%和2%的概率預測為1,這是由于訓練數據集較少,如果有更多的測試數據進行學習,預測的準確率將會有顯著提升。