Spark ML代码框架解读

1,590 阅读4分钟

写在前面: 目前做的项目中核心功能是可视化建模,是通过Spark ML改造而来。本文通过一个简单的建模流程来解读Spark ML代码框架,了解了优秀的框架是如何抽象和解剖复杂问题之后,再简单说明了解了架构之后可以在这个基础上做什么。

举个建模中的小例子

假设我们现在要建立一个评分卡模型,最简单的步骤就是“数据处理 -> 特征分箱 -> iv特征过滤 -> 建立评分卡”,数据处理咱们就只做“类型转换”,那么建模流程如下图:

每一个步骤我们都起个名字叫做stage,然后我们看每个stage的数据流:

图中,“类型转换”的输入和输出均为数据,输入的是原始数据,输出的是转换类型后的数据。而“特征分箱”因为涉及到计算woe和iv等值,会生成“分箱模型”。特征数据会命重“分箱模型”的具体分箱,然后输出特征分箱数据。所以“特征分箱”的输入为数据,输出为一个模型。而生成的“分箱模型”的输入输出均为数据。

另一边“iv特征过滤”的输入和输出均为数据,输入的是特征分箱数据,输出的是经过iv过滤的特征数据。而“评分卡”的计算可以使用逻辑回归等模型进行训练(不仅限该方法)得到“评分卡模型”。所以“评分卡”的输入是数据,输出是模型。“评分卡模型”的输入输出均为数据。

按照上述流程,可以将stage分为两类,一类是输入输出均为数据,用于数据的转换处理;另一类是将数据转换为模型,用于数据的推演计算。咱们暂且将这两种stage称之为TransformerEstimator

接下来我们得到了两个完整的模型,便可以直接在业务上使用:

显然此时该模型的输入输出均为数据。

Spark ML架构

其实上述的stageTransformerEstimator就是对应Spark ML里的PipelineStageTransformerEstimator。简单的继承关系如下图:

Spark ML中Transformer提供了transform()方法,将输入的Dataframe转换为输出的DataframeEstimator提供了fit()方法将输入的Dataframe训练为输出的Transformer。可以简单的理解为Transformer用于做数据处理,而Estimator用于生成模型。实际上Estimatorfit()方法生成的是一个Model,也就是我们说的模型,该类继承自Transformer也是靠transform()方法来做数据处理。想到我们生成的模型,最终就是做一个数据的处理再输出为一个结果,上面的描述也就能明白了。

另外,刚才我们将“类型转换”、“特征分箱”、“iv值过滤”和“评分卡”这些PipelineStage连接在一起,就能组成一个Pipeline。可以看到Pipeline继承自Estimator,所以也提供了fit()方法。方法中回循环执行每个PipelineStagetransform()fit()方法,最终生成一个PipelineModel。过程如下图:

此处贴上Spark源码片段,可能会更直观:

theStages.view.zipWithIndex.foreach { case (stage, index) =>
      if (index <= indexOfLastEstimator) {
        val transformer = stage match {
          case estimator: Estimator[_] =>
            estimator.fit(curDataset)
          case t: Transformer =>
            t
          case _ =>
            throw new IllegalArgumentException(
              s"Does not support stage $stage of type ${stage.getClass}")
        }
        if (index < indexOfLastEstimator) {
          curDataset = transformer.transform(curDataset)
        }
        transformers += transformer
      } else {
        transformers += stage.asInstanceOf[Transformer]
      }
    }

生成的PipelineModel继承自Model,所以提供了transform()方法,提供模型服务。过程如下图:

理解Spark ML架构后可以做什么

  1. 可以自定义PipelineStage,只要按照业务需求实现fit()transform()方法,也能放进Pipeline中使用;
  2. PipelinePipelineModel均为单链路,也就是一个输入对应一个输出。同样可以修改fit()transform()方法的入参出参,来实现拓扑结构;
  3. Spark ML使用的数据集为Dataframe,而Dataframe是一种分布式数据集。所以Spark ML注定只能做离线或准实时的批量数据处理。可以考虑在Transform中新增一个接口,它的输入和输出均只有一条数据;
  4. PipelineStage的运算比较黑盒,一次运行后只能看到处理结果,无法看到中间数据或某些具体算法的数据统计。比如“评分卡模型”,如果想看当前模型的评分区间,其实是看不到的。所以可以考虑新增一个接口方法,专门用于输出中间数据或数据统计。