0x00 摘要
Alink 是阿里巴巴基于实时计算引擎 Flink 研发的新一代机器学习算法平台,是业界首个同时支持批式算法、流式算法的机器学习平台。本文是漫谈系列的第二篇,将从源码入手,带领大家具体剖析Alink设计思想和架构为何。
因为Alink的公开资料太少,所以均为自行揣测,肯定会有疏漏错误,希望大家指出,我会随时更新。
书接上文,我们将讨论设计架构之算法组件部分。
0x01 中间层 -- 算法组件
算法组件是中间层的概念,可以认为是真正实现算法的模块/层次。主要作用是承上启下。
-
其上层是流水线各个阶段,流水线的生成结果就是一个算法组件。算法组件的作用是把流水线的Estimator或者Transformer翻译成具体算法。算法组件彼此是通过 linkFrom 串联在一起。
-
其下层是"迭代计算框架",算法组件把具体算法逻辑中的计算/通信分成一个个小模块,映射到Mapper Function 或者具体"迭代计算框架"的计算/通信 Function 上,这样才能更好的利用Flink的各种优势。
-
"迭代计算框架" 中,主要两个部分是 Mapper Function 和 计算/通信 Function,其在代码中分别对应Mapper,ComQueueItem。
-
Mapper Function 是映射Function(系统写好了部分Mapper,用户也可以根据算法来写自己的Mapper);
-
计算/通信 Function是专门为算法写的专用Function(也分成 系统内置的,算法自定义的)。
-
可以这么理解:各种Function是业务逻辑(组件)。算法组件只是提供运行规则,业务逻辑(组件)作为运行在算法组件上的插件。
-
也可以这么理解 :算法组件就是框架,其把部分业务逻辑委托给Mapper或者ComQueueItem。
比如
-
KMeans 是 Estimator,其对应算法组件是 KMeansTrainBatchOp。其业务逻辑(组件)也在这个类中,是由IterativeComQueue为基础串联起来的一系列算法类(ComQueueItem)。
-
VectorAssembler 是 Transformer,其对应算法组件是 MapBatchOp。其业务逻辑(组件)是VectorAssemblerMapper(其 map 函数会做业务逻辑,把将多个数值列按顺序汇总成一个向量列)。
public final class KMeansTrainBatchOp extends BatchOperator implements KMeansTrainParams
// class for a flat map BatchOperator. public class MapBatchOp<T extends MapBatchOp> extends BatchOperator
无论是调用Estimator.fit 还是 Transformer.transform,其本质都是通过linkFrom函数,把各个Operator联系起来,这样就把数据流串联起来。然后就可以逐步映射到Flink具体运行计划上。
1. Algorithm operators
AlgoOperator是算子组件的基类,其子类有BatchOperator和StreamOperator,分别对应了批处理和流处理。
// Base class for algorithm operators.
public abstract class AlgoOperator<T extends AlgoOperator<T>>
implements WithParams<T>, HasMLEnvironmentId<T>, Serializable
// Base class of batch algorithm operators.
public abstract class BatchOperator<T extends BatchOperator <T>> extends AlgoOperator <T> {
// Link this object to BatchOperator using the BatchOperators as its input.
public abstract T linkFrom(BatchOperator <?>... inputs);
public <B extends BatchOperator <?>> B linkTo(B next) {
return link(next);
}
public BatchOperator print() throws Exception {
return linkTo(new PrintBatchOp().setMLEnvironmentId(getMLEnvironmentId()));
}
}
public abstract class StreamOperator<T extends StreamOperator <T>> extends AlgoOperator <T>
示例代码如下:
// 输入csv文件被转化为一个BatchOperator
BatchOperator data = new CsvSourceBatchOp().setFilePath(URL).setSchemaStr(SCHEMA_STR);
...
pipeline.fit(data).transform(data).print();
2. Mapper(提前说明)
Mapper是底层迭代计算框架的一部分,是业务逻辑(组件)。从目录结构能看出。这里提前说明,是因为在流水线讲解过程中大量涉及,所以就提前放在这里说明。
./java/com/alibaba/alink/common
linalg mapper model comqueue utils io
Mapper的几个主要类定义如下,其作用广泛,即可以映射输入到输出,也可以映射模型到具体数值。
// Abstract class for mappers.
public abstract class Mapper implements Serializable {}
// Abstract class for mappers with model.
public abstract class ModelMapper extends Mapper {}
// Find the closest cluster center for every point.
public class KMeansModelMapper extends ModelMapper {}
// Mapper with Multi-Input columns and Single Output column(MISO).
public abstract class MISOMapper extends Mapper {}
// This mapper maps many columns to one vector. the columns should be vector or numerical columns.
public class VectorAssemblerMapper extends MISOMapper {}
Mapper的业务逻辑依赖于算法组件来运作,比如 [ VectorAssemblerMapper in MapBatchOp ] ,[ KMeansModelMapper in ModelMapBatchOp ]。
ModelMapper具体运行则需要依赖 ModelMapperAdapter 来和Flink runtime联系起来。ModelMapperAdapter继承了RichMapFunction,ModelMapper作为其成员变量,在map操作中执行业务逻辑,ModelSource则是数据来源。
对应本实例,KMeansModelMapper 就是最后转换的 BatchOperator,其map函数用来转换。
3. 系统内置算法组件
系统内置了一些常用的算法组件,比如:
-
MapBatchOp 功能是基于输入来flat map,是 VectorAssembler 返回的算法组件。
-
ModelMapBatchOp 功能是基于模型进行flat map,是 KMeans 返回的算法组件。
以 ModelMapBatchOp 为例给大家说明其作用,从下面代码注释中可以看出,linkFrom作用是:
-
把inputs"算法组件" 和 本身"算法组件" 联系起来,这就形成了一个算法逻辑链。
-
把业务逻辑映射成 "Flink算子",这就形成了一个 "Flink算子链"。
public class ModelMapBatchOp<T extends ModelMapBatchOp> extends BatchOperator { @Override public T linkFrom(BatchOperator<?>... inputs) { checkOpSize(2, inputs);
try { BroadcastVariableModelSource modelSource = new BroadcastVariableModelSource(BROADCAST_MODEL_TABLE_NAME); // mapper是映射函数 ModelMapper mapper = this.mapperBuilder.apply( inputs[0].getSchema(), inputs[1].getSchema(), this.getParams()); // modelRows 是模型 DataSet modelRows = inputs[0].getDataSet().rebalance(); // resultRows 是输入数据的映射变化 DataSet resultRows = inputs[1].getDataSet() .map(new ModelMapperAdapter(mapper, modelSource)) // 把模型作为广播变量,后续会在 ModelMapperAdapter 中使用 .withBroadcastSet(modelRows, BROADCAST_MODEL_TABLE_NAME);
TableSchema outputSchema = mapper.getOutputSchema(); this.setOutput(resultRows, outputSchema); return (T) this; } catch (Exception ex) { throw new RuntimeException(ex); } } }
ModelMapperAdapter
ModelMapperAdapter 是适配器的实现,用来在flink上运行业务逻辑Mapper。从代码可以看出,ModelMapperAdapter取出之前存储的mapper和模型数据,然后基于此来进行具体算法业务。
/**
* Adapt a {@link ModelMapper} to run within flink.
* This adapter class hold the target {@link ModelMapper} and it's {@link ModelSource}. Upon open(), it will load model rows from {@link ModelSource} into {@link ModelMapper}.
*/
public class ModelMapperAdapter extends RichMapFunction<Row, Row> implements Serializable {
/**
* The ModelMapper to adapt.
*/
private final ModelMapper mapper;
/**
* Load model data from ModelSource when open().
*/
private final ModelSource modelSource;
public ModelMapperAdapter(ModelMapper mapper, ModelSource modelSource) {
// mapper是业务逻辑,modelSource是模型Broadcast source
this.mapper = mapper; // 在map操作中执行业务逻辑
this.modelSource = modelSource; // 数据来源
}
@Override
public void open(Configuration parameters) throws Exception {
// 从广播变量中获取模型数据
List<Row> modelRows = this.modelSource.getModelRows(getRuntimeContext());
this.mapper.loadModel(modelRows);
}
@Override
public Row map(Row row) throws Exception {
// 执行业务逻辑,在数据来源上转换
return this.mapper.map(row);
}
}
4. 训练阶段fit
在 pipeline.fit(data)之中,会沿着流水线依次执行。如果流水线下一个阶段遇到了是Transformer,就调用其transform;如果遇到的是EstimatorBase,就先调用其fit,把EstimatorBase转换为Transformer,然后再次调用这个转换出来的Transformer.transform。就这样一个一个阶段执行。
4.1 具体流水线处理
-
如果流水线下一阶段遇到EstimatorBase,会处理EstimatorBase的fit,把流水线上的Estimator转换为 TransformerBase。Estimator.fit 接受一个特征数据并产生一个转换器。
(如果这个阶段 不是 流水线最后一个阶段)会对这个 TransformerBase继续处理。处理之后才能进入到流水线下一阶段。
(如果这个阶段 是 流水线最后一个阶段)不会对这个 TransformerBase 做处理,直接结束流水线 fit 操作。
-
如果流水线下一阶段遇到TransformerBase,就直接调用其transform函数。
-
对于所有需要处理的TransformerBase,无论是从EstimatorBase转换出来的,还是Pipeline原有的 ,都调用其transform函数,转换其input。
input = transformers[i].transform(input);。这样每次转换后的输出再次赋值给input,作为流水线下一阶段的输入。 -
最后得到一个PipelineModel (其本身也是一个Transformer) ,这个属于下一阶段转换流水线。
4.2 结合本实例概述
本实例有两个stage。VectorAssembler是Transformer,KMeans是EstimatorBase。
这时候Pipeline其内部变量是:
this = {Pipeline@1195}
stages = {ArrayList@2851} size = 2
0 = {VectorAssembler@1198}
mapperBuilder = {VectorAssembler$lambda@2857}
params = {Params@2858} "Params {outputCol="features", selectedCols=["sepal_length","sepal_width","petal_length","petal_width"]}"
1 = {KMeans@2856}
params = {Params@2860} "Params {vectorCol="features", maxIter=100, reservedCols=["category"], k=3, predictionCol="prediction_result", predictionDetailCol="prediction_detail"}"
params = {HashMap@2862} size = 6
-
Pipeline 先调用Transformer类型的VectorAssembler,来处理其input (就是csv的BatchOperator)。这个处理csv是通过linkFrom(input)来构建的。处理之后再包装成一个MapBatchOp返回赋值给input。
-
其次调用EstimatorBase类型的Kmeans.fit函数,对input (就是 VectorAssembler 返回的MapBatchOp) 进行fit。fit过程中调用了KMeansTrainBatchOp.linkFrom来设置,fit生成了一个KMeansModel (Transformer)。因为这时候已经是流水线最后一步,所以不做后续的KMeansModel.transform操作。KMeansModel 就是训练出来的判断模型。
-
在上述调用过程中,会在transformers数组中记录运算过的TransformerBase和EstimatorBase适配出来的Transformer。
-
最后以这个transformers数组为参数,生成一个 PipelineModel (其也是一个Transformer类型)。生成 PipelineModel 的目的是:PipelineModel是后续转换中的新流水线。
PipelineMode 的新流水线处理流程是:从 csv 读入/ 映射(VectorAssembler 处理),然后 KMeansModel 做转换(下一节会具体介绍)。
fit 具体代码是
public class Pipeline extends EstimatorBase<Pipeline, PipelineModel> {
// Train the pipeline with batch data.
public PipelineModel fit(BatchOperator input) {
int lastEstimatorIdx = getIndexOfLastEstimator();
TransformerBase[] transformers = new TransformerBase[stages.size()];
for (int i = 0; i < stages.size(); i++) {
PipelineStageBase stage = stages.get(i);
if (i <= lastEstimatorIdx) {
if (stage instanceof EstimatorBase) {
// 这里会把流水线上的具体 Algorithm operators 通过 linkFrom 函数串联起来。
transformers[i] = ((EstimatorBase) stage).fit(input);
} else if (stage instanceof TransformerBase) {
transformers[i] = (TransformerBase) stage;
}
// 注意,如果是流水线最后一个阶段,则不做transform处理。
if (i < lastEstimatorIdx) {
// 这里会调用到具体Transformer的transform函数,其会把流水线上的具体 Algorithm operators 通过 linkFrom 函数串联起来。
input = transformers[i].transform(input);
}
} else {
transformers[i] = (TransformerBase) stage;
}
}
// 这里生成了一个PipelineModel,transformers会作为参数传给他
return new PipelineModel(transformers).setMLEnvironmentId(input.getMLEnvironmentId());
}
}
// MapTransformer是VectorAssembler的基类。transform会生成一个MapBatchOp,然后再调用MapBatchOp.linkFrom。
public abstract class MapTransformer<T extends MapTransformer <T>>
extends TransformerBase<T> implements LocalPredictable {
@Override
public BatchOperator transform(BatchOperator input) {
return new MapBatchOp(this.mapperBuilder, this.params).linkFrom(input);
}
}
// Trainer是KMeans的基类。
public abstract class Trainer<T extends Trainer <T, M>, M extends ModelBase<M>>
@Override
public M fit(BatchOperator input) {
// KMeans.train 会调用 KMeansTrainBatchOp(this.getParams()).linkFrom(in);
// createModel会生成一个新的model,本示例中是 com.alibaba.alink.pipeline.clustering.KMeansModel
return createModel(train(input).getOutputTable());
}
}
下面会逐一论述这两个环节。
4.3 VectorAssembler.transform
这部分作用是把csv数据转化为KMeans训练所需要的数据类型。
VectorAssembler.transform会调用到MapBatchOp.linkFrom。linkFrom首先把 csv input 进行了转换,变成DataSet,然后以此为参数生成一个MapBatchOp返回,这个返回的 MapBatchOp。其业务逻辑是在 VectorAssemblerMapper 中实现的(将多个数值列按顺序汇总成一个向量列)。
public class MapBatchOp<T extends MapBatchOp<T>> extends BatchOperator<T> {
public T linkFrom(BatchOperator<?>... inputs) {
BatchOperator in = checkAndGetFirst(inputs);
try {
Mapper mapper = (Mapper)this.mapperBuilder.apply(in.getSchema(), this.getParams());
// 这里对csv输入进行了map,这里只是生成逻辑执行计划,具体操作会在print之后才做的。
DataSet<Row> resultRows = in.getDataSet().map(new MapperAdapter(mapper));
TableSchema resultSchema = mapper.getOutputSchema();
this.setOutput(resultRows, resultSchema);
return this;
} catch (Exception var6) {
throw new RuntimeException(var6);
}
}
}
// MapBatchOp本身
this = {MapBatchOp@3748} "UnnamedTable$1"
mapperBuilder = {VectorAssembler$lambda@3744}
params = {Params@3754} "Params {outputCol="features", selectedCols=["sepal_length","sepal_width","petal_length","petal_width"]}"
output = {TableImpl@5862} "UnnamedTable$1"
sideOutputs = null
// mapper就是业务逻辑模块
mapper = {VectorAssemblerMapper@5785}
handleInvalid = {VectorAssemblerMapper$HandleType@5813} "ERROR"
outputColsHelper = {OutputColsHelper@5814}
colIndices = {int[4]@5815}
dataFieldNames = {String[5]@5816}
dataFieldTypes = {DataType[5]@5817}
params = {Params@5818} "Params {outputCol="features", selectedCols=["sepal_length","sepal_width","petal_length","petal_width"]}"
// 返回数值如下
resultRows = {MapOperator@5788}
function = {MapperAdapter@5826}
mapper = {VectorAssemblerMapper@5785}
defaultName = "linkFrom(MapBatchOp.java:35)"
// 调用栈如下
linkFrom:31, MapBatchOp (com.alibaba.alink.operator.batch.utils)
transform:34, MapTransformer (com.alibaba.alink.pipeline)
fit:122, Pipeline (com.alibaba.alink.pipeline)
main:31, KMeansExample (com.alibaba.alink)
4.4 KMeans.fit
这部分就是训练模型。
KMeans是一个Trainer,其进而实现了EstimatorBase类型,所以流水线就调用到了其fit函数
KMeans.fit就是调用了Trainer.fit。
-
Trainer.fit首先调用train函数,最终调用KMeansTrainBatchOp.linkFrom,这样就和VectorAssembler串联起来。KMeansTrainBatchOp 把VectorAssembler返回的 MapBatchOp进行处理。最后返回一个同样类型KMeansTrainBatchOp。
-
Trainer.fit其次调用Trainer.createModel,该函数会根据this的类型决定应该生成什么Model。对于 KMeans,就生成了KMeansModel。
因为KMeans是流水线最后一个阶段,这时候不调用 input = transformers[i].transform(input); 所以目前还是训练,生成一个模型 KMeansModel。
// 实际部分代码
Trainer.fit(BatchOperator input) {
return createModel(train(input).getOutputTable());
}
public final class KMeansTrainBatchOp extends BatchOperator <KMeansTrainBatchOp>
implements KMeansTrainParams <KMeansTrainBatchOp> {
public KMeansTrainBatchOp linkFrom(BatchOperator <?>... inputs) {
DataSet <Row> finalCentroid = iterateICQ(initCentroid, data,
vectorSize, maxIter, tol, distance, distanceType, vectorColName, null, null);
this.setOutput(finalCentroid, new KMeansModelDataConverter().getModelSchema());
return this;
}
}
// 变量内容
this = {KMeansTrainBatchOp@5887}
params = {Params@5895} "Params {vectorCol="features", maxIter=100, reservedCols=["category"], k=3, predictionCol="prediction_result", predictionDetailCol="prediction_detail"}"
output = null
sideOutputs = null
inputs = {BatchOperator[1]@5888}
0 = {MapBatchOp@3748} "UnnamedTable$1"
mapperBuilder = {VectorAssembler$lambda@3744}
params = {Params@3754} "Params {outputCol="features", selectedCols=["sepal_length","sepal_width","petal_length","petal_width"]}"
output = {TableImpl@5862} "UnnamedTable$1"
sideOutputs = null
// 调用栈如下
linkFrom:84, KMeansTrainBatchOp (com.alibaba.alink.operator.batch.clustering)
train:31, KMeans (com.alibaba.alink.pipeline.clustering)
fit:34, Trainer (com.alibaba.alink.pipeline)
fit:117, Pipeline (com.alibaba.alink.pipeline)
main:31, KMeansExample (com.alibaba.alink)
KMeansTrainBatchOp.linkFrom是算法重点。这里其实就是生成了算法所需要的一切前提,把各种Flink算子搭建好。后续会再提到。
fit函数生成了 KMeansModel,其transform函数在基类MapModel中实现,会在下一个transform阶段完成调用。这个就是训练出来的KMeans模型,其也是一个Transformer。
// Find the closest cluster center for every point.
public class KMeansModel extends MapModel<KMeansModel>
implements KMeansPredictParams <KMeansModel> {
public KMeansModel(Params params) {
super(KMeansModelMapper::new, params);
}
}
4.5 生成新的转换流水线
前面说到了,Pipeline的fit函数,返回一个PipelineModel。这个PipelineModel在后续会继续调用transform,完成转换阶段。
return new PipelineModel(transformers).setMLEnvironmentId(input.getMLEnvironmentId());
5. 转换阶段transform
转换阶段的流水线,依然要从VectorAssembler入手来读取csv,进行map处理。然后调用 KMeansModel。
PipelineModel会继续调用transform函数。其作用是把Transformer转化为BatchOperator。这时候其内部变量如下,看出来已经从最初流水线各种类型参杂 转换为 统一transform实例。
this = {PipelineModel@5016}
transformers = {TransformerBase[2]@5017}
0 = {VectorAssembler@1198}
mapperBuilder = {VectorAssembler$lambda@2855}
params = {Params@2856} "Params {outputCol="features", selectedCols=["sepal_length","sepal_width","petal_length","petal_width"]}"
1 = {KMeansModel@5009}
mapperBuilder = {KMeansModel$lambda@5011}
modelData = {TableImpl@4984} "UnnamedTable$2"
params = {Params@5012} "Params {vectorCol="features", maxIter=100, reservedCols=["category"], k=3, predictionCol="prediction_result", predictionDetailCol="prediction_detail"}"
modelData = null
params = {Params@5018} "Params {MLEnvironmentId=0}"
-
第一次transform调用到了MapBatchOp.linkFrom,就是VectorAssembler.transform调用到的,其作用和 在 fit 流水线中起到的作用一样,下面注释中有解释。
-
第二次transform调用到了ModelMapBatchOp.linkFrom,就是KMeansModel.transform间接调用到的。下面注释中有解释。
这两次 transform 的调用生成了 BatchOperator 的串联。最终返回结果是 ModelMapBatchOp,即一个BatchOperator。转换将由ModelMapBatchOp来转换。
// The model fitted by Pipeline.
public class PipelineModel extends ModelBase<PipelineModel> implements LocalPredictable {
@Override
public BatchOperator<?> transform(BatchOperator input) {
for (TransformerBase transformer : this.transformers) {
input = transformer.transform(input);
}
return input;
}
}
// 经过变化后,得到一个最终的转化结果 BatchOperator,以此来转换
// {KMeansModel$lambda@5050} 就是 KMeansModelMapper,转换逻辑。
input = {ModelMapBatchOp@5047} "UnnamedTable$3"
mapperBuilder = {KMeansModel$lambda@5050}
params = {Params@5051} "Params {vectorCol="features", maxIter=100, reservedCols=["category"], k=3, predictionCol="prediction_result", predictionDetailCol="prediction_detail"}"
params = {HashMap@5058} size = 6
"vectorCol" -> ""features""
"maxIter" -> "100"
"reservedCols" -> "["category"]"
"k" -> "3"
"predictionCol" -> ""prediction_result""
"predictionDetailCol" -> ""prediction_detail""
output = {TableImpl@5052} "UnnamedTable$3"
tableEnvironment = {BatchTableEnvironmentImpl@5054}
operationTree = {DataSetQueryOperation@5055}
operationTreeBuilder = {OperationTreeBuilder@5056}
lookupResolver = {LookupCallResolver@5057}
tableName = "UnnamedTable$3"
sideOutputs = null
// MapTransformer是VectorAssembler的基类。transform会生成一个MapBatchOp,然后再调用MapBatchOp.linkFrom。
public abstract class MapTransformer<T extends MapTransformer <T>>
extends TransformerBase<T> implements LocalPredictable {
@Override
public BatchOperator transform(BatchOperator input) {
return new MapBatchOp(this.mapperBuilder, this.params).linkFrom(input);
}
}
// MapModel是KMeansModel的基类,transform会生成一个ModelMapBatchOp,然后再调用ModelMapBatchOp.linkFrom。
public abstract class MapModel<T extends MapModel<T>>
extends ModelBase<T> implements LocalPredictable {
@Override
public BatchOperator transform(BatchOperator input) {
return new ModelMapBatchOp(this.mapperBuilder, this.params)
.linkFrom(BatchOperator.fromTable(this.getModelData())
.setMLEnvironmentId(input.getMLEnvironmentId()), input);
}
}
在这两个linkFrom中,还是分别生成了两个MapOperator,然后拼接起来,构成了一个 BatchOperator 串。从上面代码中可以看出,KMeansModel对应的ModelMapBatchOp,其linkFrom会返回一个ModelMapperAdapter。ModelMapperAdapter是一个RichMapFunction类型,它会把KMeansModelMapper作为RichMapFunction.function成员变量保存起来。然后会调用 .map(new ModelMapperAdapter(mapper, modelSource)),map就是Flink算子,这样转换算法就和Flink联系起来了。
最后 Keans 算法的转换工作是通过 KMeansModelMapper.map 来完成的。
6. 运行
我们都知道,Flink程序中,为了让程序运行,需要
-
获取execution environment : 调用类似
getExecutionEnvironment()来获取environment; -
触发程序执行 : 调用类似
env.execute("KMeans Example");来真正执行。
Alink其实就是一个Flink应用,只不过要比普通Flink应用复杂太多。
但是从实例代码中,我们没有看到类似调用。这说明Alink封装的非常好,但是作为好奇的程序员,我们需要知道究竟这些调用隐藏在哪里。
获取执行环境
Alink是在Pipeline执行的时候,获取到运行环境。具体来说,因为csv文件是最初的输入,所以当transform调用其 in.getSchema() 时候,会获取运行环境。
public final class CsvSourceBatchOp extends BaseSourceBatchOp<CsvSourceBatchOp>
implements CsvSourceParams<CsvSourceBatchOp> {
@Override
public Table initializeDataSource() {
ExecutionEnvironment execEnv = MLEnvironmentFactory.get(getMLEnvironmentId()).getExecutionEnvironment();
}
}
initializeDataSource:77, CsvSourceBatchOp (com.alibaba.alink.operator.batch.source)
getOutputTable:52, BaseSourceBatchOp (com.alibaba.alink.operator.batch.source)
getSchema:180, AlgoOperator (com.alibaba.alink.operator)
linkFrom:34, MapBatchOp (com.alibaba.alink.operator.batch.utils)
transform:34, MapTransformer (com.alibaba.alink.pipeline)
fit:122, Pipeline (com.alibaba.alink.pipeline)
main:31, KMeansExample (com.alibaba.alink)
触发程序运行
截止到现在,Alink已经做了很多东西,也映射到了 Flink算子上,那么究竟什么地方才真正和Flink联系起来呢?
print 调用的是BatchOperator.print,真正从这里开始,会一层一层调用下去,最后来到
package com.alibaba.alink.operator.batch.utils;
public class PrintBatchOp extends BaseSinkBatchOp<PrintBatchOp> {
@Override
protected PrintBatchOp sinkFrom(BatchOperator in) {
this.setOutputTable(in.getOutputTable());
if (null != this.getOutputTable()) {
try {
// 在这个 collect 之后,会进入到 Flink 的runtime之中。
List <Row> rows = DataSetConversionUtil.fromTable(getMLEnvironmentId(), this.getOutputTable()).collect();
batchPrintStream.println(TableUtil.formatTitle(this.getColNames()));
for (Row row : rows) {
batchPrintStream.println(TableUtil.formatRows(row));
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return this;
}
}
在 LocalEnvironment 这里把Alink和Flink的运行环境联系起来。
public class LocalEnvironment extends ExecutionEnvironment {
@Override
public String getExecutionPlan() throws Exception {
Plan p = createProgramPlan(null, false);
// 下面会真正的和Flink联系起来。
if (executor != null) {
return executor.getOptimizerPlanAsJSON(p);
}
else {
PlanExecutor tempExecutor = PlanExecutor.createLocalExecutor(configuration);
return tempExecutor.getOptimizerPlanAsJSON(p);
}
}
}
// 调用栈如下
execute:91, LocalEnvironment (org.apache.flink.api.java)
execute:820, ExecutionEnvironment (org.apache.flink.api.java)
collect:413, DataSet (org.apache.flink.api.java)
sinkFrom:40, PrintBatchOp (com.alibaba.alink.operator.batch.utils)
sinkFrom:18, PrintBatchOp (com.alibaba.alink.operator.batch.utils)
linkFrom:31, BaseSinkBatchOp (com.alibaba.alink.operator.batch.sink)
linkFrom:17, BaseSinkBatchOp (com.alibaba.alink.operator.batch.sink)
link:89, BatchOperator (com.alibaba.alink.operator.batch)
linkTo:239, BatchOperator (com.alibaba.alink.operator.batch)
print:337, BatchOperator (com.alibaba.alink.operator.batch)
main:31, KMeansExample (com.alibaba.alink)
0xFF 参考
Spark ML简介之Pipeline,DataFrame,Estimator,Transformer
斩获GitHub 2000+ Star,阿里云开源的 Alink 机器学习平台如何跑赢双11数据“博弈”?|AI 技术生态论
★★★★★★关于生活和技术的思考★★★★★★
微信公众账号:罗西的思考