當前位置: 首頁>>算法&結構>>正文


基於Hadoop MapReduce的矩陣乘法實現

本文簡單介紹一種最基本的矩陣乘法的實現方法。

設有矩陣A(M×N)和矩陣B(N×K),令C=A*B, 那麽矩陣C(M×K)的元素為:

gif.latex其中Cik是C的第i行第k列的元素;Aij是A的第i行第j列的元素;Bjk是B的第j行第k列的元素。

注意:如果A和B可乘,那麽A的列數等於B的行數。

對於小數據量下的矩陣運算,可以直接在單機內存中根據上述公式直接計算得到。對於大數據下的矩陣運算,我們可以把上述公式搬到hadoop上來實現。

通常在Hadoop上通過MapReduce 實現矩陣運算需要兩輪:第一步計算A和B對應的元素兩兩乘積(Aij * Bjk);第二步,對所有的j, 將上一步得到的Aij*Bjk的乘積求和。具體說明如下:

  • 第一輪
    • Map: 將矩陣A,B的元素分別表示成鍵值對的形式,其中A的元素Aij表示為j => (A, i, Aij), B的元素Bjk表示為j => (B, k, Bjk)。
    • Reduce: 對於每個鍵j, 將j對應的所有A中的元素和對應的所有B中的元素兩兩相乘(Aij*Bjk),輸出i,k => Aij * Bjk,其中i,k組合在一起作為輸出的鍵值。
  • 第二輪
    • Map: 直接做cat操作(上一輪的i,k為key, 對應的乘積為value)
    • Reduce: 將組合鍵i,k對應的所有value求和。輸出i,k => sum即為結果矩陣的元素(第i行第k列)。

上述算法的時間負責度: 由於計算結果矩陣C的每個元素需要將矩陣A的整行和B的整列對應的元素相乘,其時間複雜度為O(N),而C總共有M*K個,所以總的時間複雜度為O(M*K*N),如果M=N=K,就是O(N3)。[注:目前最好的矩陣乘法時間負責度為O(N2.367),其中strassen的方法是O(Nlog7)≈O(N2.8074),他采用分治思想並將最後2×2小矩陣的計算時間從8次減少到7次]

上述兩輪MapReduce的具體實現如下:

  1. 第一輪的hadoop streaming控製程序
    
    #!/bin/bash
    #矩陣乘法, MR第一輪計算
    if [ $# -ne 0 ]
    then
    	echo "Usage: $0"
    	exit 1
    fi
    
    INPUT_DIR=/data/fuqingchuan/matrix/A4x4.txt,/data/fuqingchuan/matrix/B4x3.txt
    OUTPUT_DIR=/data/fuqingchuan/matrix/one/
    STREAM_FILE="/home/hadoop/hadoop-2.3.0-cdh5.1.0/share/hadoop/tools/lib/hadoop-streaming-2.3.0-cdh5.1.0.jar"
    ${HADOOP_HOME}/bin/hadoop fs -rm -r ${OUTPUT_DIR}
    ${HADOOP_HOME}/bin/hadoop jar ${STREAM_FILE} \
    	-D mapreduce.job.name="matrix-one-fuqingchuan" \
    	-D mapreduce.job.reduces=100 \
    	-D mapreduce.job.priority=NORMAL \
    	-D mapreduce.job.map.capacity=100 \
    	-D mapreduce.job.reduce.capacity=100 \
    	-D mapreduce.job.reduce.slowstart.completedmaps=0.95 \
    	-D stream.num.map.output.key.fields=1 \
    	-D stream.memory.limit=1000 \
    	-D mapreduce.map.memory.mb=1000 \
    	-D mapreduce.reduce.memory.mb=1000 \
    	-D mapreduce.reduce.failures.maxpercent=1 \
    	-input ${INPUT_DIR} \
    	-output ${OUTPUT_DIR} \
    	-mapper "python mapper_one.py" \
    	-reducer "python reducer_one.py" \
    	-file ./mapper_one.py \
    	-file ./reducer_one.py
    
    exit 0
    
  2. 第一輪的mapper(以j作為Key):
    
    #!/usr/bin/python
    #coding=utf8
    #matrix multiplier : first round.
    #input: two matrix, A(mxn), B(nxk)
    #output: (j A i Aij) or (j B k Bjk)
    import sys;
    import logging as log;
    if __name__ == "__main__":
    	for rawline in sys.stdin:
    		line = rawline.rstrip();
    		if len(line) <= 0:
    			continue;	
    		fields = line.split(" ");
    		flen = len(fields);
    		if flen < 3:
    			log.warning("invalid input line:%s"%line.rstrip());
    			continue;
    		name = fields[0];
    		if name != "A" and name != "B":
    			log.warning("invalid matrix name:%s. It should be A|B.");
    			continue;
    		row = fields[1];
    		for idx in range(2, flen):
    			value = fields[idx];
    			if name == "A":  # Aij => j A i Aij
    				i = row;
    				j = idx - 2;
    				print "%d\t%s\t%s\t%s"%(j, name, i, value);
    			else: # Bjk => j B k Bjk
    				j = row;
    				k = idx - 2;
    				print "%s\t%s\t%d\t%s"%(j, name, k, value);
    	sys.exit(0);
    

     

  3. 第一輪的reducer(計算乘積,並以i,k作為Key輸出):
    
    #!/usr/bin/python
    #coding=utf8
    #matrix multiplier : first round.
    #input: a matrix[Aij -> ith row, jth col]
    #output: 
    import sys;
    import logging as log;
    
    def output(listA, listB):
    	for Aij in listA:
    		for Bjk in listB:
    			A_i = Aij[0];
    			A_j = Aij[1];
    			A_v = Aij[2];
    			B_j = Bjk[0];
    			B_k = Bjk[1];
    			B_v = Bjk[2];
    			if A_j != B_j:
    				continue;
    			print "%d,%d\t%d"%(A_i, B_k, A_v * B_v);
    				
    if __name__ == "__main__":
    	listA = [];
    	listB = [];
    	last_j = None;
    	for rawline in sys.stdin:
    		line = rawline.rstrip();
    		if len(line) <= 0:
    			continue;	
    		fields = line.split("\t");
    		flen = len(fields);
    		if flen != 4:
    			log.warning("invalid input line:%s"%line.rstrip());
    			continue;
    		j = int(fields[0]);
    		name = fields[1];
    		value = int(fields[3]);
    		if last_j == None or j == last_j:
    			pass;
    		else:
    			output(listA, listB);
    			listA = [];
    			listB = [];
    
    		if name == "A":
    			i = int(fields[2]);
    			listA.append((i, j, value));
    		else: #B
    			k = int(fields[2]);
    			listB.append((j, k, value));
    
    		last_j = j;
    	if last_j != None:
    		output(listA, listB);
    
    	sys.exit(0);
    

     

  4. 第二輪的hadoop streaming總控程序:
    
    #!/bin/bash
    #矩陣乘法, MR第二輪計算
    if [ $# -ne 0 ]
    then
    	echo "Usage: $0"
    	exit 1
    fi
    
    INPUT_DIR=/data/fuqingchuan/matrix/one/
    OUTPUT_DIR=/data/fuqingchuan/matrix/two/
    STREAM_FILE="/home/hadoop/hadoop-2.3.0-cdh5.1.0/share/hadoop/tools/lib/hadoop-streaming-2.3.0-cdh5.1.0.jar"
    ${HADOOP_HOME}/bin/hadoop fs -rm -r ${OUTPUT_DIR}
    ${HADOOP_HOME}/bin/hadoop jar ${STREAM_FILE} \
    	-D mapreduce.job.name="matrix-two-fuqingchuan" \
    	-D mapreduce.job.reduces=100 \
    	-D mapreduce.job.priority=NORMAL \
    	-D mapreduce.job.map.capacity=100 \
    	-D mapreduce.job.reduce.capacity=100 \
    	-D mapreduce.job.reduce.slowstart.completedmaps=0.95 \
    	-D stream.num.map.output.key.fields=1 \
    	-D stream.memory.limit=1000 \
    	-D mapreduce.map.memory.mb=1000 \
    	-D mapreduce.reduce.memory.mb=1000 \
    	-D mapreduce.reduce.failures.maxpercent=1 \
    	-input ${INPUT_DIR} \
    	-output ${OUTPUT_DIR} \
    	-mapper "cat" \
    	-reducer "python reducer_two.py" \
    	-file ./reducer_two.py
    
    exit 0
    

     

  5. 第二輪mapper直接cat就可以了。
  6. 第二輪reducer(求和,以i,k作為Key輸出):
    
    #!/usr/bin/python
    #coding=utf8
    #matrix multiplier : first round.
    #input: a matrix[Aij -> ith row, jth col]
    #output: 
    import sys;
    import logging as log;
    
    def output(last_ik, sum):
    	print "%s\t%d"%(last_ik, sum);	
    if __name__ == "__main__":
    	last_ik = None;
    	sum = 0;
    	for rawline in sys.stdin:
    		line = rawline.rstrip();
    		if len(line) <= 0:
    			continue;	
    		fields = line.split("\t");
    		flen = len(fields);
    		if flen != 2:
    			log.warning("invalid input line:%s"%line.rstrip());
    			continue;
    		ik = fields[0];
    		value = int(fields[1]);
    		if last_ik == None or ik == last_ik:
    			sum += value;
    		else:
    			output(last_ik, sum);
    			sum = value;
    		last_ik = ik;
    	if last_ik != None:
    		output(last_ik, sum);
    
    	sys.exit(0);
    
本文由《純淨天空》出品。文章地址: https://vimsky.com/zh-tw/article/837.html,未經允許,請勿轉載。