迭代
迭代算法出现在数据分析的许多领域,如机器学习或图分析。这些算法对于实现大数据从数据中提取有意义信息的承诺至关重要。随着人们越来越有兴趣将这些算法在非常大的数据集上运行,则需要以大规模并行的方式执行迭代。
Flink通过定义一个迭代函数(step function)并将其嵌入到一个特殊的迭代操作符中来实现迭代算法。主要分为以下两类:Iterate和Delta Iterate。这两个操作符都在当前迭代状态上反复调用迭代函数,直到达到终止条件。
Iterate Operator
Iterate Operator是迭代的简单形式:在每个迭代中,step function使用整个输入(前一个迭代的结果,或初始数据集),并计算得到局部结果(例如map、reduce、join等)。

- Iteration Input(初始迭代输入):来自数据源或数据源第一次通过迭代器运算的结果。
- Step Function(迭代函数): 每一轮迭代将调用迭代函数操纵输入的数据集,如map,reduce。
- Next Partial Solution(迭代输出): 在每次迭代中,step funtion的输出将作为下一轮迭代的输入.
- Iteration Result(迭代结果): 迭代终止时的输出,因此必须定义迭代终止条件,通常有以下两种形式:
- Maximum number of iterations(最大迭代次数): 如果没有其他终止条件,迭代将在最大次数以内结束.
- Custom aggregator convergence(自定义收敛聚合函数):可自定义聚合函数和收敛条件。
示例:用蒙特卡罗方法计算π
蒙洛卡特思想的核心就是:假设这里有一个半径为1的圆,它的面积S=PiR2=Pi,所以我们只要计算出这个圆的面积就可以计算出圆周率了。这里我们可以在一个边长为1的正方形中计算圆的四分之一扇形的面积,这样扇形的面积的4倍就是整个圆的面积了。如何计算扇形的面积?可以使用概率的方法,假设在这个正方形中有n个点,那么有m个点落在了扇形中,那么S扇形:S正方形=m:n。这样就可以计算出扇形的面积,最终计算出圆周率了。
package com.ztland.flink_demo.dataSet;
import java.util.Random;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.IterativeDataSet;
public class ItreationDemo {
public static void main(String[] args) throws Exception {
final ExecutionEnvironment env=ExecutionEnvironment.getExecutionEnvironment();
Random random=new Random(1);
// 设置迭代次数
IterativeDataSet<Integer> iterativeDataSet=env.fromElements(0).iterate(100000);
DataSet<Integer> mapResult=iterativeDataSet.map(new MapFunction<Integer, Integer>() {
@Override
public Integer map(Integer value) throws Exception {
double x=random.nextDouble();
double y=random.nextDouble();
value+=(x*x+y*y<=1)?1:0;
return value;
}
});
//迭代结束的条件
DataSet<Integer> result=iterativeDataSet.closeWith(mapResult);
result.map(new MapFunction<Integer, Double>() {
@Override
public Double map(Integer count) throws Exception {
return count/(double)100000*4;
}
}).print();
}
}
示例:递增数字
在下面的例子中,我们迭代地增加一个集合中的数值:

-
Iteration Input:初始数据从数据源读取,由5条单字段记录组成(整数1到5)。
-
Step Function: step function是一个map(),它将数据源中每个integer字段从i递增到i+1。
-
Next Partial Solution: step function的输出将是map()的输出,即递增整数。
-
Iteration Result: 经过10次迭代,初始数将增加10,得到整数11到15.
// 1st 2nd 10th map(1) -> 2 map(2) -> 3 ... map(10) -> 11 map(2) -> 3 map(3) -> 4 ... map(11) -> 12 map(3) -> 4 map(4) -> 5 ... map(12) -> 13 map(4) -> 5 map(5) -> 6 ... map(13) -> 14 map(5) -> 6 map(6) -> 7 ... map(14) -> 15
Delta Iterate Operator
这种迭代方式称为增量迭代,它并不是每次去迭代全量的数据,而只是迭代一部分热点数据。通常热点数据相对数据总数来书较小,如下图所示:

- Iteration Input(初始迭代输入):来自数据源或数据源第一次通过迭代器运算的结果。
- Step Function(迭代函数): 每一轮迭代将调用迭代函数操纵输入的数据集,如map,reduce。
- Next Workset/Update Solution Set(迭代输出Workset、更新Solution Set): 每一轮将迭代作用于Workset,并迭代输出的Workset作为下一轮迭代的Workset。此外,每一轮跌打计算过程中,均可能更新迭代输入的Solution Set。
- Iteration Result(迭代结果): 迭代终止时的输出Solution Set,终止条件默认为Workset为空或达到最大迭代次数。
示例:连通体最小传播值
- 初始输入为两个数据集,每个数据集对应一个连通域,分别称为上连通域和下连通域。
- 每一个元素都有自己的颜色特征,且各不相同。
- 每个元素对应图中的一个顶点,由三元组组成,即顶点ID、顶点数值、顶点特征。其中顶点数值和顶点ID同为图中的数字

整个迭代过程如下:
- 第一次迭代中,ID为1的顶点将特征传播给ID为2的顶点,ID为2的顶点将特征传播给ID为3、4的顶点;ID为5的顶点传递给ID为6、7的顶点。ID1、5已经特征传递出去,所以不再参加后续迭代,即不再是wordSet的元素。
- 第二次迭代时下连通域的workSet仅有6、7两个顶点,进行一轮迭代传播特征就收敛了。由于此时两个顶点的特征都来自于5,所以进行传播的过程并没有发生实质替换。
- 第三次迭代将上连通域ID为2的顶点的特征,即初始状态为1的顶点特征,传播给ID为3、4的顶点。
上述过程中workset元素的数量是递减的,为空时迭代结束。
代码如下:
package com.ztland.flink_demo.dataSet;
import org.apache.flink.api.common.functions.FlatJoinFunction;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.JoinFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.aggregation.Aggregations;
import org.apache.flink.api.java.operators.DeltaIteration;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;
public class ItreationDemo {
public static void main(String[] args) throws Exception {
final ExecutionEnvironment env=ExecutionEnvironment.getExecutionEnvironment();
int iterativeNum=100;
//顶点
DataSet<Long> vertix=env.fromElements(1L,2L,3L,4L,5L,6L,7L);
//边
DataSet<Tuple2<Long,Long>> edges=env.fromElements(
Tuple2.of(1L, 2L),
Tuple2.of(2L, 3L),
Tuple2.of(2L, 4L),
Tuple2.of(3L, 4L),
Tuple2.of(5L, 6L),
Tuple2.of(5L, 7L),
Tuple2.of(6L, 7L)
);
//单向边转为双向边
edges=edges.flatMap(new FlatMapFunction<Tuple2<Long,Long>, Tuple2<Long,Long>>() {
@Override
public void flatMap(Tuple2<Long, Long> tuple, Collector<Tuple2<Long, Long>> collector) throws Exception {
collector.collect(tuple);
collector.collect(Tuple2.of(tuple.f1,tuple.f0));
}
});
//initialSolutionSet,将顶点映射为(vertix,vertix)的形式
DataSet<Tuple2<Long,Long>> initialSolutionSet=vertix.map(new MapFunction<Long, Tuple2<Long, Long>>() {
@Override
public Tuple2<Long, Long> map(Long vertix) throws Exception {
return Tuple2.of(vertix,vertix);
}
});
//initialWorkSet
DataSet<Tuple2<Long,Long>> initialWorkSet=vertix.map(new MapFunction<Long, Tuple2<Long, Long>>() {
@Override
public Tuple2<Long, Long> map(Long vertix) throws Exception {
return Tuple2.of(vertix,vertix);
}
});
//第一个字段做迭代运算
DeltaIteration<Tuple2<Long,Long>,Tuple2<Long,Long>> iterative=
initialSolutionSet.iterateDelta(initialWorkSet,iterativeNum,0);
//数据集合边做join操作,然后求出当前顶点的邻居顶点的最小ID值
DataSet<Tuple2<Long,Long>> changes=iterative.getWorkset().join(edges).where(0).equalTo(0).with(new NeighborWithComponentIDJoin())
.groupBy(0).aggregate(Aggregations.MIN,1)
//和solution set进行join操作,更新solution set,如果当前迭代结果中的最小ID小于solution中的ID值,则发送到下一次迭代运算中继续运算,否则不发送
.join(iterative.getSolutionSet()).where(0).equalTo(0)
.with(new ComponetIDFilter());
//关闭迭代计算
DataSet<Tuple2<Long,Long>> result=iterative.closeWith(changes,changes);
result.print();
}
public static class NeighborWithComponentIDJoin implements JoinFunction<Tuple2<Long,Long>,Tuple2<Long,Long>,Tuple2<Long,Long>>{
@Override
public Tuple2<Long, Long> join(Tuple2<Long, Long> t1, Tuple2<Long, Long> t2) throws Exception {
return Tuple2.of(t2.f1,t1.f1);
}
}
public static class ComponetIDFilter implements FlatJoinFunction<Tuple2<Long,Long>,Tuple2<Long,Long>,Tuple2<Long,Long>> {
@Override
public void join(Tuple2<Long, Long> t1, Tuple2<Long, Long> t2, Collector<Tuple2<Long, Long>> collector) throws Exception {
if(t1.f1<t2.f1){
collector.collect(t1);
}
}
}
}
结果:
(3,1)
(7,5)
(6,5)
(1,1)
(5,5)
(2,1)
(4,1)
1154

被折叠的 条评论
为什么被折叠?



