对MapReduce也用了一段时间,基本的数据处理都已比较熟悉,但目前用的S1的参数和直觉调的参数。这样下去不行啊,果断要开搞机器学习。虽然阿里给我们提供了Xlab,看起来也挺强大的,各种模型都很丰富,但作为一个不造轮子会死的程序猿,还是想实现自己的训练方法和模型,这也有利于后期的提高,所以就找了点资料研究了如何用MapReduce进行机器学习的训练。
MapReduce的入门,可以参考这个文章:http://www.yumumu.me/MapReduce_In_Odps/
本文主要参考了这篇论文Zinkevich1,并实现了一个线性回归的实例,实现很简单,主要想起到抛砖引玉的作用,欢迎大家一起讨论,提出更优的解决办法。
随机梯度下降法的(SGD)基本算法
SGD的基本算法如下:
Algorithm 1 SGD({c(1), . . . , c(m)}, T, η, w0)
for t = 1 to T do
Draw j ∈ {1 . . . m} uniformly at random.
w(t) = w(t−1) − η*∂w(c(j))*w(t−1);
end for
return wT
c(i) 表示第i个样本的损失函数,η为学习速率,∂w为w的梯度,T为迭代次数。 算法的主要思想就是每次从样本中随机抽取一个样本,用来训练。
如果要在MapReduce中实现SGD,理论上算法如下:
Algorithm 2 ParallelSGD({c(1), . . . , c(m)}, T, η, w0,k)
for all i ∈ {1, . . . k} parallel do
v(i) = SGD({c(1), . . . , c(m)}, T, η, w0) on client
end for
Aggregate from all computers v =sum(v(1),...,v(k))/k and return v
这个算法需要在每台机器上进行随机梯度下降。但由于使用MapReduce,每次Mapper里面只能是一条条记录读入,无法进行随机选择,所以最终算法如下:
Algorithm 3 SimuParallelSGD({c(1), . . . , c(m)}, T, η, w0,k) for all i ∈ {1, . . . k} parallel do Randomly shuffle the data on machine i. Initialize w(i,0) = 0. for all t ∈ {1, . . . T }: do Get the (t)th example on the (i)th machine (this machine), w(i,t) = w(i,t−1) − η*∂w(c(i))*w(i,t−1); end for Aggregate from all computers v =sum(w(1,t),...,v(k,t))/k and return
算法3先把数据进行打乱,并平均分配到每台机器上。每台机器不再进行数据的随机选择,而是一条条读入进行训练,最后再把每台机器的训练结果汇总取平均。
实例:线性回归的MapReduce训练
由于是第一次使用MapReduce,上述算法的实现需要几个条件,现在进行一一测试确认。
- 在MapReduce中,每个独立的Mapper对象中是否能够共享变量,且和其他mapper对象独立,因为训练需要保存上一次的结果。这里我在Mapper中使用了一个类内的全局变量count,然后每次调用一次map函数就累加,count如果大于10就不再输出。最后系统产生了259个Mapper,生成的表有2590行数据,这说明每个Mapper对象内的变量是共享且独立的。
- 能否自己指定Mapper和Reducer的数量,最后结果要汇总到一台机器,所以Reducer数量必须为1。这个看文档也是可以实现的。但需要注意的是,Mapper的数量直接设置貌似没有效果,需要靠设置spiltSize来调整Mapper大小。
现在就用最简单的一元线性回归来实现一个随机梯度下降的MapperReduce训练过程,损失函数为最小平方和。
(以下代码未测试)
SGDMapper代码:
public class SGDReduce extends ReducerBase{
Record result;
public setup(TaskContext context) throws IOException{
result = context.createOutputRecord();
}
public void reduce(Record key, Iterator <Record> values,TaskContext context) throws IOException{
int count = 0;
double b0 = 0;
double b1 = 0;
while(values.hasNext())
{
Record val = values.next();
b0 += val.getDouble(0);
b1 += val.getDouble(1);
count += 1;
}
b0 /= count;
b1 /= count;
result.set(0,b0);
result.set(1,b1);
context.write(result);
}
}
其是需要批量的迭代训练的,这可以在Driver的main函数中调用多次MR任务,并用Jobconf.set 和 get来保存每一次迭代的结果。
此外,在Driver的Main函数中还可以调用sql语句。这就可以利用表作为中转,执行级联的MapReduce任务。