MapReduce编程: 2. 矩阵乘法

MapReduce编程: 2. 矩阵乘法

题目

使用MapReduce编程实现矩阵乘法

分析
  1. 矩阵的存储方式,有两种,采用原始表示方式,就是矩阵是什么样子,就保存成什么样子,比如
    在这里插入图片描述

还有一种,就是以稀疏矩阵存储,仅存储非零值,比如
在这里插入图片描述
两种存储方式各有所长,我们就采用第二种存储方式

  1. 在矩阵乘法运算时,是用左矩阵A的第i行的元素 分别乘以 右矩阵B的第j列的各个元素,然后结果相加,从而得到结果矩阵C的第i行第j列的元素,所以可以得到这样一个规律:Aij,会用在Cim,m=1,2,3,…,j;同理,Bij,会用在Cnj,n=1,2,3,…,i
  2. 对于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)>,说的我自己都晕了,后面放张图去理解吧
  3. 对于reduce,要做的就是将shuffle的结果,进行合并,计算结果,所以输入就是map的输出,输出就是结果矩阵
  4. 根据map和reduce的输入输出,键值对都是好几个数字的组合,所以可以使用Text类
  5. 对于上篇博客没玩透的combiner,我当然又会继续作死的玩,但是还是好多坑,在后面说吧

上篇博客:https://blog.csdn.net/Dongroot/article/details/88571680

准备工作
  • ubuntu14环境
  • 启动的hadoop
  • 安装好hadoop插件的eclipse
  • 存放两个矩阵的文档

(以下操作都是在ubuntu下进行)对于前三个环境,应该在上篇博客之前就已经弄好了,就不说了,只要准备两个文档就OK了,在桌面新建两个txt文档,按照上面介绍的第二种存储方式,写上两个矩阵,比如
在这里插入图片描述
这里我是采用两个文本,然后使用英文空格隔开,你们也可以使用一个文本存储,也可以用逗号隔开,但是等会后面的程序读取文本的时候要记得改。
文档准备好之后,将文档上传至HDFS,然后准备工作就好了

上代码
  1. 打开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前,进行相关变量或者资源的集中初始化工作,在一个文件读取完成之前只会执行一次,因为我是用的两个文件保存矩阵,所以我要根据文件名来区分是哪个矩阵

  1. 创建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));
	}
}
  1. 创建驱动程序,先不使用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);
		}
	}

}

因为要读取两个文件,所以输入就要俩

  1. 这个时候是正确的,运行看一下
    在这里插入图片描述
    可以对照上面的两个矩阵,结果完全正确,下面开始作妖,来使用combiner玩一下

  2. 先解释一下combiner,combiner的作用就是将本地的map的结果进行整合,然后发送给reduce,所以combiner的输入是reduce的输入,输出是map的输出,一般来说,combiner就是做的和reduce一样的事,那直接放开 job.setCombinerClass(MyReducer.class);的注释,我们先用写好的reduce试一下,结果
    在这里插入图片描述
    没有报错,但是结果emmmmmmm

  3. 分析一下,我的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

解题思路来自老师上课的讲解

  • 2
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值