MapReduce编程: 2. 矩阵乘法
题目
使用MapReduce编程实现矩阵乘法
分析
- 矩阵的存储方式,有两种,采用原始表示方式,就是矩阵是什么样子,就保存成什么样子,比如
还有一种,就是以稀疏矩阵存储,仅存储非零值,比如
两种存储方式各有所长,我们就采用第二种存储方式
- 在矩阵乘法运算时,是用左矩阵A的第i行的元素 分别乘以 右矩阵B的第j列的各个元素,然后结果相加,从而得到结果矩阵C的第i行第j列的元素,所以可以得到这样一个规律:Aij,会用在Cim,m=1,2,3,…,j;同理,Bij,会用在Cnj,n=1,2,3,…,i
- 对于map,要做的就是将原始的两个矩阵上的各个数,会参与结果矩阵的哪一个位置的运算找出来,所以输入依旧是原始文本,输出是一个键值对,键是该元素会参与运算的位置,是一个行列坐标,值则是该元素的标志,a代表左矩阵,b代表右矩阵,同时保存该元素的位置和值,打个比方,在左矩阵第三行第二列的元素9,它的存储为 3 2 9,假设它会参与到C31和C32的运算,所以经过map,输出为 <(3,1),(a,2,9)>和<(3,2),(a,2,9)>,而如果是右矩阵第三行第二列的元素9,存储还是 3 2 9,那么它会参与到C12和C22的运算,经过map,输出为 <(1,2),(b,3,9)>和<(2,2),(b,3,9)>,说的我自己都晕了,后面放张图去理解吧
- 对于reduce,要做的就是将shuffle的结果,进行合并,计算结果,所以输入就是map的输出,输出就是结果矩阵
- 根据map和reduce的输入输出,键值对都是好几个数字的组合,所以可以使用Text类
- 对于上篇博客没玩透的combiner,我当然又会继续作死的玩,但是还是好多坑,在后面说吧
上篇博客:https://blog.csdn.net/Dongroot/article/details/88571680
准备工作
- ubuntu14环境
- 启动的hadoop
- 安装好hadoop插件的eclipse
- 存放两个矩阵的文档
(以下操作都是在ubuntu下进行)对于前三个环境,应该在上篇博客之前就已经弄好了,就不说了,只要准备两个文档就OK了,在桌面新建两个txt文档,按照上面介绍的第二种存储方式,写上两个矩阵,比如
这里我是采用两个文本,然后使用英文空格隔开,你们也可以使用一个文本存储,也可以用逗号隔开,但是等会后面的程序读取文本的时候要记得改。
文档准备好之后,将文档上传至HDFS,然后准备工作就好了
上代码
- 打开eclipse,新建MapReduce Project,建包,创建Mapper类
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
/**
*
* @author Dong
*
*/
public class MyMapper extends Mapper<Object, Text, Text, Text> {
// 左矩阵的行数
private static final int TEST1_ROW = 4;
// 右矩阵的列数
private static final int TEST2_COL = 2;
// 左矩阵的文件名称
private static final String TEST1 = "test1.txt";
// 右矩阵的文件名称
private static final String TEST2 = "test2.txt";
// 临时保存当前文件名称
private String test;
@Override
protected void setup(Mapper<Object, Text, Text, Text>.Context context)
throws IOException, InterruptedException {
// 获取当前读取的文件的文件名
FileSplit fileSplit = (FileSplit) context.getInputSplit();
test = fileSplit.getPath().getName();
}
@Override
protected void map(Object key, Text value, Mapper<Object, Text, Text, Text>.Context context)
throws IOException, InterruptedException {
/*
* map输入:
* key1,当前记录在文件中的位置
* value1,原始文本
*
* map输出:
* key2,该数据在结果的矩阵中的位置 (行,列)
* value2,两种情况
* 左矩阵: (a,列,值)
* 右矩阵: (b,行,值)
*/
// 切割原始文本,分离值和位置
String[] values = value.toString().split(" ");
// 判断是哪个文件的内容
if(MyMapper.TEST1.equals(test)) {
// 说明是左矩阵的内容
for(int i = 1;i <= MyMapper.TEST2_COL;i++) {
context.write(
new Text(values[0]+" "+i),
new Text("a"+" "+values[1]+" "+values[2])
);
}
}
else if(MyMapper.TEST2.equals(test)) {
// 是右矩阵
for(int i = 1;i <= MyMapper.TEST1_ROW;i++) {
context.write(
new Text(i+" "+values[1]),
new Text("b"+" "+values[0]+" "+values[2])
);
}
}
}
}
这里说明一下,为了程序方便,我直接将两个矩阵的行列数,还有两个文件的名字写在了代码里面,然后解释一下setup方法,就是在执行Map前,进行相关变量或者资源的集中初始化工作,在一个文件读取完成之前只会执行一次,因为我是用的两个文件保存矩阵,所以我要根据文件名来区分是哪个矩阵
- 创建Reducer类
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
*
* @author Dong
*
*/
public class MyReducer extends Reducer<Text, Text, Text, Text> {
@Override
protected void reduce(Text text, Iterable<Text> iterable,
Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
/*
* reduce输入:
* key1,该数据在结果的矩阵中的位置 (行,列)
* value1,两种情况
* 左矩阵: (a,列,值)
* 右矩阵: (b,行,值)
*
* reduce输出:
* key2,结果的矩阵中的位置 (行,列)
* value2,结果值
*/
// 提取输入的value1中的数据
Map<Integer, Integer> test1Map = new HashMap<Integer, Integer>();
Map<Integer, Integer> test2Map = new HashMap<Integer, Integer>();
String[] values = null;
for(Text t : iterable) {
values = t.toString().split(" ");
if("a".equals(values[0])) {
// 说明是左矩阵
test1Map.put(Integer.parseInt(values[1]), Integer.parseInt(values[2]));
}
else if("b".equals(values[0])) {
// 说明是右矩阵
test2Map.put(Integer.parseInt(values[1]), Integer.parseInt(values[2]));
}
}
// 计算结果,矩阵对应位置相乘,没有的为0
int result = 0;
Set<Integer> set = test1Map.keySet();
for(Integer i : set) {
if(test2Map.get(i) == null) {
// 说明在该位置的值为0,就没必要相加了
continue;
}
else {
result += (test1Map.get(i) * test2Map.get(i));
}
}
// 输出结果
context.write(text, new Text(""+result));
}
}
- 创建驱动程序,先不使用combiner
import java.io.IOException;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
/**
*
* @author Dong
*
*/
public class Matrix {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
String[] path = {
"hdfs://localhost:9000/user/hduser/matrix/input/test1.txt",
"hdfs://localhost:9000/user/hduser/matrix/input/test2.txt",
"hdfs://localhost:9000/user/hduser/matrix/output"
};
Job job = Job.getInstance(new Configuration());
job.setJarByClass(Matrix.class);
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// job.setCombinerClass(MyReducer.class);
FileInputFormat.setInputPaths(job,new Path(path[0]),new Path(path[1]));
FileOutputFormat.setOutputPath(job, new Path(path[2]));
boolean a = job.waitForCompletion(true);
if(a) {
System.exit(0);
System.out.println(0);
}
else {
System.exit(1);
System.out.println(1);
}
}
}
因为要读取两个文件,所以输入就要俩
-
这个时候是正确的,运行看一下
可以对照上面的两个矩阵,结果完全正确,下面开始作妖,来使用combiner玩一下 -
先解释一下combiner,combiner的作用就是将本地的map的结果进行整合,然后发送给reduce,所以combiner的输入是reduce的输入,输出是map的输出,一般来说,combiner就是做的和reduce一样的事,那直接放开 job.setCombinerClass(MyReducer.class);的注释,我们先用写好的reduce试一下,结果
没有报错,但是结果emmmmmmm -
分析一下,我的reduce是直接计算结果的,那么按照概念,我的reduce作为combiner,输入确实是reduce的输入,但是输出可不是map的输出了,所以结果当然是错的,百度一下,发现这么说
看来reduce和combiner并不是完全一样的,而且也并不是所有情况都适用,像类似于求中值的问题,需要所有部分的结果,才能的得到答案的,就不适合,而像求最大值这样的,并不需要所有的结果,只需要每一部分的最大值,去比较,就可以得到答案,这种就可以使用combiner。所以上个求比例的题,和这个矩阵乘法的,都是需要所有部分才能计算答案的,就不适合使用combiner(放一张MapReduce处理矩阵的图)
问题总结
- 这次已经不记得报了多少次报jvm内存不足了,因为我使用的是伪分布式的,所以所有的运算都在本地机,而虚拟机内存又不大,所以报内存不足很正常,我曾作死的把虚拟机内存调到6g,然后导致电脑卡死了,然后关了虚拟机,又调到3.5g才没有报内存不足
- 对于combiner的使用还是很多坑,不过两次使用之后,理解好像又稍微透那么一点点
- 好像这次就出现了内存不足的错误,也没有别的错误,对MapReduce编程的运作原理,也不是很难,这次难的还是对map的输出的分析,这个点理解了,后面也能很简单的做出来了
资料参考
这是我百度combiner的那篇博客:https://blog.csdn.net/guoery/article/details/8529004
解题思路来自老师上课的讲解