使用MapReduce对svm模型进行训练

SVM模型有两个非常重要的参数C与gamma。其中 C是惩罚系数,即对误差的宽容度。c越高,说明越不能容忍出现误差。C过大或过小,泛化能力变差
gamma是选择RBF函数作为kernel后,该函数自带的一个参数。隐含地决定了数据映射到新的特征空间后的分布,gamma越大,支持向量越少,gamma值越小,支持向量越多。支持向量的个数影响训练与预测的速度。

Grid Search

Grid Search是用在Libsvm中的参数搜索方法。很容易理解:就是在C,gamma组成的二维参数矩阵中,依次实验每一对参数的效果。

使用grid Search虽然比较简单,而且看起来很naïve。但是他确实有两个优点:

  1. 可以得到全局最优
  2. (C,gamma)相互独立,便于并行化进行
正是因为有这样的性质,所以才使得可以利用MapReduce对模型进行训练

Grid Search in MapReduce


基本思路:
     MapReduce分为Map和Reduce两个阶段,Map阶段会将数据切片,分发到不同的机器上。在Reduce阶段会将具有相同Key的进行汇总。
结合我们现在的需求,我们的目的是想通过MR将SVM的参数实验分布在不同的机器上做,理想状况下,每台机子做实验一组参数,这样整个参数寻优的所花费的时间就等同于一组参数花费的时间了。
这里有两种方案:
  1. 将参数集合和训练样本作为本地文件都上传到Hadoop上,这样在每个Mapper上就可以直接读取这两个文件,然后任选一组参数进行训练,并把结果输出。
  2. 将参数集合作为本地文件上传的Hadoop,而将训练样本作为输入,这样Hadoop会自动的将训练样本切片,每个Mapper得到的只是其中的一部分数据。因此就需要在Map中按照参数对数据进行汇总,并在Reduce阶段才真正的对SVM进行训练。
方案一很直观,但是却不能调控每个Mapper所训练的参数。因此我们选择第二种方案。

其步骤如下
  1. 首先将所有的参数对(c,gamma)计算出来,写入文件中,每行一组参数,然后上传到Hadoop上,系统会自动的将文件分发到每一台机器上。
  2. 将训练样本作为输入,即Map中的每对就代表着一条样本,其中key为系统默认值,value为训练样本的每一行数据。将所有的参数对读入到list中
  3. 在Map阶段。把输入的数据作为value,list中每组参数对作为key 进行输出
  4. 在Reduce阶段,Hadoop会按照key进行排序,并将key相同的数据放在同一个机器上,因此在此阶段我们需要将属于同一个key的所有values汇总组成原来的训练样本。
  5. 把解析出来的参数以及整理好的样本进行训练,并把交叉验证的结果输出

具体的程序如下:
#!/usr/bin/env python
#_*_ coding: utf-8 _*_
import sys
import os
sys.path.append('.')
from hstream import *
sys.path.append(os.path.join(os.path.dirname(__file__),"dependence"))
import tms_svm
'''
first step:先把整个程序流程跑通
seconde step:将输入参数文件和svm_type参数化。
'''
def read_param(filename):
    params=list()
    for line in file(filename):
        params.append(line.strip())
    return params

default_param_file="./params"
svm_type="libsvm"
params = read_param(default_param_file)

class SvmTrain(HStream):
    '''input 为SVM训练需要的数据'''

#    def __init__(self,param_file=default_param_file):
#        pass
#        self.parse_args()
#        print self.default_param_file
#        self.param_file=param_file
#        self.params = self.read_param(self.param_file)

    def mapper(self,key, value):
        '''mapper function
            将参数作为key,样本作为value进行输出
        '''
        for param in params:
            self.write_output( param, value )

    def reducer(self, key, values):
        '''reducer function'''
        prob_y=[]
        prob_x=[]
        line=key.split(None,1)
        #设置SVM训练的参数
        if sum([1 for i in line])==1:
            svm_param = " -v 5 -c "+str(line[0])
        else :
            if sum([1 for i in line])>=2:
                svm_param = " -v 5 -c "+str(line[0])+" -g "+str(line[1])

        #对训练样本进行汇总整理
        for value in values:
            value = value.split(None,1)
            if len(value)==1: value+=['']
            label, features = value
            xi={}
            for e in features.split():
                ind, val = e.split(":")
                xi[int(ind)] = float(val)
            prob_y +=[float(label)]
            prob_x +=[xi]

        #对得到的参数与训练样本进行训练
        tms_svm.set_svm_type(svm_type)
        ratio = tms_svm.train(prob_y,prob_x,svm_param)
        self.write_output( key, str(ratio))
    
#    def parse_args(self):
#        parser = OptionParser(usage="")
#        parser.add_option("-p", "--paramFile",help="param filename",dest="paramFile")
#        options,args = parser.parse_args()
#        if options.paramFile:
#            self.default_param_file=options.paramFile
        

    
if __name__ == '__main__':
    SvmTrain()

有了上面的程序,我们通过运行Hadoop就可以得到结果了

$HADOOP jar $STREAMINGJAR -D mapred.job.name='Svm_Train' \
    -D mapred.reduce.tasks=100 \
    -files hstream.py,svm_train.py,params,${depen_path}/svm.py,${depen_path}/svmutil.py,${depen_path}/liblinear.py,${depen_path}/liblinearutil.py,${depen_path}/measure.py,${depen_path}/segment.py,${depen_path}/strnormalize.py,${depen_path}/tms_svm.py,${depen_path}/liblinear.so.64,${depen_path}/libsvm.so.64 \
    -mapper "svm_train.py -m" \
    -reducer "svm_train.py -r" \
    -input ${INPUT} \
    -output ${OUTPUT}

总结

本文使用MapReduce方法对SVM的参数寻优过程进行并行操作,可极大的缩短SVM训练的时间。
另外本文的方法还有两点值得注意的地方:
  1. 原本在SvmTrain程序中想实现对default_param_file、svm_type、svm_param 这3个变量由用户随意设定(parse_args函数),但是实验了好几次都没有成功。
  2. 其实加速grid search的方法还有一种:就是首先在训练样本的子集上使用粗粒度进行搜索,假如得到最优值为(1,0.5),然后在C区间[1-step,1+step]和gamma区间[0.5-step,0.5+step] 组成的矩阵中进行细粒度的搜索,所谓粗粒度和细粒度,就是指的步长step不同而已,使用这种方法也可以加快搜索的速度,且可以在大部分情况下得到全局最优。


你可能感兴趣的:(数据挖掘)