並行化大矩陣乘法是較早的基於MapReduce編程模型實現的基礎算法之一,最早是由Google公司爲了解決PageRank中包含的大量矩陣乘法而提出的。今天我們就來一起學習一下基於MapReduce的並行化大矩陣乘法。
我們假設有兩個矩陣M和N,其中M的列數等於N的行數,則記M和N的乘積P = M . N。其中Mij表示矩陣M中第i行第j列的元素,Njk表示矩陣N中第j行第K列的元素,則矩陣P中的元素可由下式求得:
即Pik爲M的第i行元素和N的第k列元素對應相乘再相加。由上式我們知道最終決定Pik位置的是(i,k),我們可以把(i,k)作爲Reduce輸出的key,將Pik作爲輸出的value。爲了求出Pik,我們必須知道Mij和Njk。對於Mij,我們需要知道的屬性有所屬矩陣爲M,行號爲i,列號爲j,Mij本身的值大小。對於Njk,我們需要知道的屬性有所屬矩陣爲N,行號爲j,列號爲k,Njk本身的值大小。Mij和Njk的屬性都有Mapper類處理產生。
Map函數:對於M矩陣的每一個元素Mij,產生一系列的鍵值對<(i,k),(M,j,Mij)>,其中K=1、2、到N的列數。對於N矩陣中的每一個元素Njk,產生一系列的鍵值對<(i,k),(N,j,Njk)>,其中i=1、2、到M的行數。
Reduce函數:對於同一個鍵(i,k),有許多的值(M,j,Mij)、(N,j,Njk),將j值相同的Mij和Njk相乘,然後不同j值處理後的結果再相加,即可得到Pik的值。
下面我們就以一個具體的矩陣爲例講解。
我們將M矩陣存放在M.txt文件中,文件的一行爲一個元素,內容格式爲“元素所在行,元素所在列 元素值”。M.txt的內容如下。
我們將N矩陣存放在N.txt中,N.txt的內容如下。
Map函數輸出:經過map函數的處理,產生了一系列形如<(i,k),(M,j,Mij)>的鍵值對,具體如下。
Reduce函數輸出:對相同的(i,k)鍵,按照j值進行相乘再相加,過程如下。
所以最終得到的乘積矩陣爲P = [2,5,11]。
並行化大矩陣乘法的MapReduce程序如下:
package Matrix;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
/**
*parameters : rowM,columnM,columnN,InputPaths,OutputPath
* @author liuchen
*
*/
public class MatrixMain {
public static void main(String[] args)throws Exception {
//create job = map + reduce
Configuration conf = new Configuration();
//Setting global share parameters
conf.set("rowM", args[0]);
conf.set("columnM", args[1]);
conf.set("columnN", args[2]);
//create Job
Job job = Job.getInstance(conf);
//the entry of job
job.setJarByClass(MatrixMain.class);
//the mapper of job
job.setMapperClass(MatrixMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//the reducer of job
job.setReducerClass(MatrixReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//input and output
TextInputFormat.setInputPaths(job, new Path(args[3]));
TextOutputFormat.setOutputPath(job, new Path(args[4]));
//submit job
job.waitForCompletion(true);
}
}
package Matrix;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
/**
* Matrix multiplication Mapper
* @author liuchen
*
*/
public class MatrixMapper extends Mapper<Object, Text , Text , Text> {
private static int columnN = 0;
private static int rowM = 0;
private Text map_key = new Text();
private Text map_value = new Text();
/**
* Before executing the map function, get the necessary parameters
*/
protected void setup(Context context)throws IOException, InterruptedException {
Configuration conf = context.getConfiguration();
columnN = Integer.parseInt(conf.get("columnN"));
rowM = Integer.parseInt(conf.get("rowM"));
}
protected void map(Object key, Text value,Context context)throws IOException, InterruptedException {
//Through filename differentiation matrix
FileSplit fileSplit = (FileSplit)context.getInputSplit();
String fileName = fileSplit.getPath().getName();
if(fileName.contains("M")){ //M Matrix
String[] arr1 = value.toString().split(",");
int i = Integer.parseInt(arr1[0]);
String[] arr2 = arr1[1].split("\t");
int j = Integer.parseInt(arr2[0]);
int Mij = Integer.parseInt(arr2[1]);
for(int k = 1;k <= columnN;k++){
map_key.set(i + "," + k);
map_value.set("M," + j + "," + Mij);
context.write(map_key, map_value);
}
}
else if (fileName.contains("N")){ //N Matrix
String[] arr1 = value.toString().split(",");
int j = Integer.parseInt(arr1[0]);
String[] arr2 = arr1[1].split("\t");
int k = Integer.parseInt(arr2[0]);
int Njk = Integer.parseInt(arr2[1]);
for(int i = 1;i<= rowM;i++){
map_key.set(i + "," + k);
map_value.set("N," + j +"," + Njk);
context.write(map_key, map_value);
}
}
}
}
package Matrix;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class MatrixReducer extends Reducer<Text, Text, Text, Text>{
private static int columnM = 0;
protected void setup(Context context)throws IOException, InterruptedException {
Configuration conf = context.getConfiguration();
columnM = Integer.parseInt(conf.get("columnM"));
}
protected void reduce(Text key, Iterable<Text> values,Context context)throws IOException, InterruptedException {
int[] M = new int[columnM + 1]; //Index is 0 Empty
int[] N = new int[columnM + 1];
int sum = 0;
for(Text value : values){
String[] arr1 = value.toString().split(",");
if(arr1[0].contains("M")){
M[Integer.parseInt(arr1[1])] = Integer.parseInt(arr1[2]);
}
else if (arr1[0].contains("N")){
N[Integer.parseInt(arr1[1])] = Integer.parseInt(arr1[2]);
}
}
for(int j = 1;j<columnM + 1;j++){
sum += M[j] * N[j];
}
context.write(key, new Text(Integer.toString(sum)));
}
}
獲取更多幹貨請關注微信公衆號:追夢程序員。