Spark机器学习-1

ML Pipelines

ML Pipelines提供了一套基于DataFrames的高层API来帮助用户创建和调整实战中的机器学习流水线。

Pipelines主要名词

  • 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


介绍

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


介绍

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。

Parameter


介绍

MLLib中的Transformer和Estimator共用一套声明参数的API。

修改算法的参数有两种方法:

  1. 用set方法为单个实例。比如:上面Estimator中的例子,lr.setMaxIter(10),就是设置这个模型最大的迭代次数
  2. 把ParamMap类型的参数类传递给fit()或者transform()。在ParamMap里设置的参数会覆盖之前用setter方法设置的参数

示例

  • 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实例。通过打印出两次模型的参数可以看到参数的改变。

结合Transformer,Estimator,Param的Java示例

下面例子构造一个逻辑回归模型,然后对伪造的数据进行预测。

    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));

            }

        }
    }

Pipeline示例

在上面的例子中,在用训练数据创建Dataset也就是DataFrame时,我们使用StructType去构造DataFrame的schema,也就是DataFrame中的列。下面的例子将用实体类来构建DataFrame的schema。

  • 声明实体类JavaDocument作为测试样本的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;
        }
    }
  • 继承JavaDocument声明JavaLabeledDocument作为训练样本的schema,因为带了标签属性。
    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;
      }
    }
  • 把上述两个实体类用于测试Pipeline

    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));
            }
        }
    }

你可能感兴趣的:(机器学习)