ML Pipelines提供了一套基于DataFrames的高层API来帮助用户创建和调整实战中的机器学习流水线。
DataFrame: DataFrame可以存储不同类型的数据,类似于mysql数据库中的表,有不同的列可以存储文本,特征或者预测值,是ML API操作的数据集。DataFrame是从Spark SQL生成的。
Transformer:能把一个DataFrame转化成另外一个DataFrame的算法,比如一个逻辑回归模型就是一个Transformer,能够把测试样本DataFrame转化成一个带预测标签的DataFrame
Estimator:是一个通过在训练样本DataFrame上训练然后产生一个Transformer的算法,比如一个逻辑回归算法通过在一个训练集DataFrame上训练然后得到一个model,这个model就是一个Transformer。
Pipeline:一个Pipeline通过把不同的Transformer和Estimator组合到一起形成一个机器学习流程,比如,一个分词Transformer,一个词频计算Transformer,一个逻辑回归Estimator这三个就可以构成一个Pipeline,而这个Pipeline做的事情就是把输入的文本DataFrame通过分词Transformer把词提取出来,然后通过词频计算Transformer计算出每个文本的词频向量,然后通过Estimator训练得到一个最终的PipelineModel,这个model是一个Transformer,可以把测试样本transform成预测的DataFrame。
Parameter:所有的Transformer和Estimator现在都共用一套声明参数的API。
Transformer是特征转换和已训练模型的一种抽象。就比如,一个OneHotEncoder就是一个特征变换的Transformer。一般来说,Transformer都会实现一个方法为transform()来把一个DataFrame转换成另一个,通常是在原来的DataFrame上加多一列或几列。比如:
OneHotEncoder是一个特征Tansformer,会把一个有一列为性别的DataFrame转换成加多两列为性别男,性别女的DataFrame。
逻辑回归算法在训练样本DataFrame训练后会得到一个逻辑回归模型,这就是一个已训练模型Tansformer,可以通过transform(testData)方法把测试样本testData转换成一个带预测标签和原来属性的DataFrame
Tokenizer() Transfomer
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.*;
import java.util.Arrays;
import java.util.List;
List dataTraining = Arrays.asList(
RowFactory.create("spark hadoop test"),
RowFactory.create("tokenizer transformer test")
);
StructType schema = new StructType(new StructField[]{
new StructField("features", DataTypes.StringType, false, Metadata.empty())
});
Dataset training = spark.createDataFrame(dataTraining, schema);
Tokenizer tokenizer = new Tokenizer()
.setInputCol("text")
.setOutputCol("words");
Dataset words = tokenizer.transform(training);
上述例子中,spark是一个SparkSession实例。training是一个字符串的DataFrame,Tokenizer是一个Transformer,作用是把输入的字符串变为小写,然后用空格分隔成词。words就是通过Tokenizer转换得到的另一个DataFrame。
features |
---|
spark hadoop test |
tokenizer transformer test |
Tokenizer().transform()
——————————>
features | words |
---|---|
spark hadoop test | spark,hadoop,test |
tokenizer transformer test | tokenizer,transformer,test |
Estimator是算法的抽象,可以在数据上进行训练以得到Transformer。一般来说,Estimator都会实现一个叫fit()的方法,这个方法接受一个DataFrame然后返回一个Model。这个Model就是上述的Transformer。
LogisticRegression Estimator
import org.apache.spark.ml.classification.LogisticRegression;
import org.apache.spark.ml.classification.LogisticRegressionModel;
//创建一个LogisticRegression实例。这个实例是一个Estimator
LogisticRegression lr = new LogisticRegression();
//设置模型参数
lr.setMaxIter(10).setRegParam(0.01);
//训练模型,得到Transformer
LogisticRegressionModel model = lr.fit(training);
上述例子中,spark是一个SparkSession实例。lr是一个Estimator,而model是一个transformer。
MLLib中的Transformer和Estimator共用一套声明参数的API。
修改算法的参数有两种方法:
Parameter specify
import org.apache.spark.ml.classification.LogisticRegression;
import org.apache.spark.ml.classification.LogisticRegressionModel;
//创建一个LogisticRegression实例。这个实例是一个Estimator
LogisticRegression lr = new LogisticRegression();
//用setter方法设置模型参数,设置最大迭代次数为10,正则化参数为0.01
lr.setMaxIter(10).setRegParam(0.01);
//用上述参数训练模型
LogisticRegressionModel model1 = lr.fit(training);
//打印出模型使用的参数
System.out.println("Model 1 was fit using parameters: " + model1.parent().extractParamMap());
//用ParmMap来生明参数,一种是参数.w(newValue),一种是put(参数,newValue)
ParamMap paramMap = new ParamMap()
.put(lr.maxIter().w(20))
.put(lr.maxIter(), 30)
.put(lr.regParam().w(0.01), lr.threshold().w(0.55));
//用新的参数组合学习一个新的模型
//paramMap会把之前lr.set的全部覆盖掉
LogisticRegressionModel model2 = lr.fit(training, paramMapCombined);
//打印出新模型使用的参数
System.out.println("Model 2 was fit using parameters: " + model2.parent().extractParamMap());
上述例子中,spark是一个SparkSession实例。通过打印出两次模型的参数可以看到参数的改变。
下面例子构造一个逻辑回归模型,然后对伪造的数据进行预测。
import org.apache.spark.ml.classification.LogisticRegression;
import org.apache.spark.ml.classification.LogisticRegressionModel;
import org.apache.spark.ml.linalg.VectorUDT;
import org.apache.spark.ml.linalg.Vectors;
import org.apache.spark.ml.param.ParamMap;
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;
import java.util.Arrays;
import java.util.List;
/**
* Created by shying on 2017/5/11.
*/
public class EstimatorTransformerParamExample {
public static void main(String[] args) {
SparkSession spark = SparkSession
.builder()
.master("local")
.appName("example1")
.getOrCreate();
List dataTraining = Arrays.asList(
RowFactory.create(1.0, Vectors.dense(0.0, 1.1, 0.1)),
RowFactory.create(0.0, Vectors.dense(2.0, 1.0, -1.0)),
RowFactory.create(0.0, Vectors.dense(2.0, 1.3, 1.0)),
RowFactory.create(1.0, Vectors.dense(0.0, 1.2, -0.5))
);
StructType schema = new StructType(new StructField[]{
new StructField("label", DataTypes.DoubleType, false, Metadata.empty()),
new StructField("features", new VectorUDT(), false, Metadata.empty())
});
Dataset training = spark.createDataFrame(dataTraining, schema);
//创建一个LogisticRegression实例。这个实例是一个Estimator
LogisticRegression lr = new LogisticRegression();
//打印参数,文档和一些其他默认值
//System.out.println("LogisticRegression parameters:\n" + lr.explainParams() + "\n");
//设置模型参数
lr.setMaxIter(10).setRegParam(0.01);
//训练模型
LogisticRegressionModel model1 = lr.fit(training);
//打印模型训练所用的参数
//System.out.println("Model 1 was fit using parameters: " + model1.parent().extractParamMap());
//用ParmMap来生明参数,一种是参数.w(newValue),一种是put(参数,newValue)
ParamMap paramMap = new ParamMap()
.put(lr.maxIter().w(20))
.put(lr.maxIter(), 30)
.put(lr.regParam().w(0.01), lr.threshold().w(0.55));
//可以把两个ParamMap组合到一起
ParamMap paramMap2 = new ParamMap()
.put(lr.probabilityCol().w("myProbability")); //改变属性的输出名
ParamMap paramMapCombined = paramMap.$plus$plus(paramMap2);
//用新的参数组合学习一个新的模型
//paramMapCombined会把之前lr.set的全部覆盖掉
LogisticRegressionModel model2 = lr.fit(training, paramMapCombined);
System.out.println("Model 2 was fit using parameters: " + model2.parent().extractParamMap());
// 测试数据集
List dataTest = Arrays.asList(
RowFactory.create(1.0, Vectors.dense(-1.0, 1.5, 1.3)),
RowFactory.create(0.0, Vectors.dense(3.0, 2.0, -0.1)),
RowFactory.create(1.0, Vectors.dense(0.0, 2.2, -1.5))
);
Dataset test = spark.createDataFrame(dataTest, schema);
// 用Transformer.transform()对dataTest进行测试
//LogisticRegression.transform只用到feature列
//注意model2的Probability列名被我们改成了myProbability
Dataset results = model2.transform(test);
Dataset rows = results.select("features", "label", "myProbability", "prediction");
for (Row r : rows.collectAsList()) {
System.out.println("(" + r.get(0) + ", " + r.get(1) + ") -> prob=" + r.get(2)
+ ", prediction=" + r.get(3));
}
}
}
在上面的例子中,在用训练数据创建Dataset也就是DataFrame时,我们使用StructType去构造DataFrame的schema,也就是DataFrame中的列。下面的例子将用实体类来构建DataFrame的schema。
public class JavaDocument {
private long id;
private String text;
public JavaDocument(long id, String text) {
this.id = id;
this.text = text;
}
public long getId() {
return this.id;
}
public String getText() {
return this.text;
}
}
import java.io.Serializable;
/**
* Spark SQL能从Java Bean中推断出DataFrame的schema
*/
public class JavaLabeledDocument extends JavaDocument implements Serializable {
private double label;
public JavaLabeledDocument(long id, String text, double label) {
super(id, text);
this.label = label;
}
public double getLabel() {
return this.label;
}
}
import com.fierydata.entity.JavaDocument;
import com.fierydata.entity.JavaLabeledDocument;
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.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.ml.feature.Tokenizer;
import org.apache.spark.ml.feature.HashingTF;
import java.util.Arrays;
/**
* Created by shying on 2017/5/11.
*/
public class PipelineExample {
public static void main(String[] args) {
SparkSession spark = SparkSession.builder()
.master("local")
.appName("example2")
.getOrCreate();
//创建带标签的文本训练集
Dataset training = spark.createDataFrame(Arrays.asList(
new JavaLabeledDocument(0L, "a b c d e spark", 1.0),
new JavaLabeledDocument(1L, "b d", 0.0),
new JavaLabeledDocument(2L, "spark f g h", 1.0),
new JavaLabeledDocument(3L, "hadoop mapreduce", 0.0)
), JavaLabeledDocument.class);
//创建一条包括tokenizer,hashingTF和逻辑回归模型lr的ML pipeline
//Tokenizer把文本变为小写然后按空格切割成词
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.001);
Pipeline pipeline = new Pipeline()
.setStages(new PipelineStage[] {tokenizer, hashingTF, lr});
//用训练样本训练pipeline
PipelineModel model = pipeline.fit(training);
//创建测试没有标签的测试样本
Dataset test = spark.createDataFrame(Arrays.asList(
new JavaDocument(4L, "spark i j k"),
new JavaDocument(5L, "l m n"),
new JavaDocument(6L, "spark hadoop spark"),
new JavaDocument(7L, "apache hadoop")
), JavaDocument.class);
//对测试样本做预测
Dataset predictions = model.transform(test);
for (Row r : predictions.select("id", "text", "probability", "prediction").collectAsList()) {
System.out.println("(" + r.get(0) + ", " + r.get(1) + ") --> prob=" + r.get(2)
+ ", prediction=" + r.get(3));
}
}
}