MapReduce应用☞关联操作

主要是使用hadoop集群完成数据相关性简单分析

一、单表关联

单表关联"要求从给出的数据中寻找所关心的数据,它是对原始数据所包含信息的挖掘

1.1实例

给出child-parent(孩子——父母)表,要求输出grandchild-grandparent(孙子——爷奶)表。
输入:

child      parent 
Tom        Lucy
Tom        Jack
Jone       Lucy
Jone       Jack
Lucy       Mary
Lucy       Ben
Jack       Alice
Jack       Jesse
Terry      Alice
Terry      Jesse
Philip     Terry
Philip     Alma
Mark       Terry
Mark       Alma

输出:

grandchild        grandparent 
Tom              Alice
Tom              Jesse
Jone             Alice
Jone             Jesse
Tom              Mary
Tom              Ben
Jone             Mary
Jone             Ben
Philip            Alice
Philip            Jesse
Mark             Alice
Mark             Jesse

1.2 问题分析

这个实例需要进行单表连接,连接的是左表的parent列和右表的child列,且左表和右表是同一个表。
连接结果中除去连接的两列就是所需要的结果——"grandchild–grandparent"表。要用MapReduce解决这个实例,首先应该考虑如何实现表的自连接;其次就是连接列的设置;最后是结果的整理
MapReduce的shuffle过程会将相同的key会连接在一起,所以可以将map结果的key设置成待连接的列,然后列中相同的值就自然会连接在一起了。

1.3实现步骤

map阶段将读入数据分割成child和parent之后,将parent设置成key,child设置成value进行输出,并作为左表;再将同一对child和parent中的child设置成key,parent设置成value进行输出,作为右表
2.为了区分输出中的左右表,需要在输出的value中再加上左右表的信息,比如在value的String最开始处加上字符1表示左表,加上字符2表示右表
3. reduce接收到连接的结果,其中每个key的value-list就包含了"grandchild–grandparent"关系。取出每个key的value-list进行解析,将左表中的child放入一个数组,右表中的parent放入一个数组,然后对两个数组求笛卡尔积就是最后的结果了

1.5代码

static class MyMapper extends Mapper <Object, Object, Text, Text>{
		Text out_key = new Text();
		Text out_value = new Text();
		
		protected void map(Object key, Object value, Context context) 
				throws IOException, InterruptedException {
			
			String[] tokens = value.toString().trim().split(","); 
			if(tokens!=null && tokens.length==2) {
				out_key.set(tokens[0]);
				out_value.set("2,"+tokens[0]+","+tokens[1]);
				context.write(out_key, out_value);
				
				out_key.set(tokens[1]);
				out_value.set("1,"+tokens[0]+","+tokens[1]);
				context.write(out_key, out_value);
			}
		}
	}
	static class MyReduce extends Reducer <Text, Text, Text, Text> {
		 
		 Text out_key = new Text();
		 Text out_value = new Text();
		 protected void reduce(Text key, Iterable<Text> values, Context context)
				throws IOException, InterruptedException {
			  
			 List<String> child = new ArrayList<String>(); 
			 List<String> grandpa = new ArrayList<String>();
			 
			 			 
			 for(Text t:values) {
				 String[] s = t.toString().trim().split(",");
				 if(s[0].equals("1")) {
					 child.add(s[1]);
				 } else if(s[0].equals("2")) {
					 grandpa.add(s[2]);
				 }
			 }
			 
			 for(String sc:child) {
				 for(String sg:grandpa) {
					 out_key.set(sc);
					 out_value.set(sg);
					 context.write(out_key, out_value);
				 }
			 }
		 }     	
	}

二、多表关联

多表关联和单表关联类似,它也是通过对原始数据进行一定的处理,从其中挖掘出关心的信息

2.1实例描述

输入是两个文件,一个代表工厂表,包含工厂名列和地址编号列;另一个代表地址表,包含地址名列和地址编号列。要求从输入数据中找出工厂名和地址名的对应关系,输出"工厂名——地址名"表

输入

factoryname                    addressed 
Beijing Red Star                   1
Shenzhen Thunder                   3
Guangzhou Honda                    2
Beijing Rising                      1
Guangzhou Development Bank           2
Tencent                         3
Back of Beijing                    1

输入

addressID    addressname 
1            Beijing
2            Guangzhou
3            Shenzhen
4            Xian

输出

factoryname                          addressname 
Back of Beijing                      Beijing
Beijing Red Star                      Beijing
Beijing Rising                        Beijing
Guangzhou Development Bank             Guangzhou
Guangzhou Honda                     Guangzhou
Shenzhen Thunder                    Shenzhen
Tencent                           Shenzhen

2.2 问题分析

多表关联和单表关联相似,都类似于数据库中的自然连接。相比单表关联,多表关联的左右表和连接列更加清楚。所以可以采用和单表关联的相同的处理方式,map识别出输入的行属于哪个表之后,对其进行分割,将连接的列值保存在key中,另一列和左右表标识保存在value中,然后输出。reduce拿到连接结果之后,解析value内容,根据标志将左右表内容分开存放,然后求笛卡尔积,最后直接输出。

2.3代码

static class MyMapper extends Mapper <Object, Object, Text, Text>{
		Text out_key = new Text();
		Text out_value = new Text();
		String table = "";
		protected void setup(Context context)
				throws java.io.IOException, java.lang.InterruptedException {
			FileSplit fs = (FileSplit) context.getInputSplit();
			table = fs.getPath().getName();
		}
	    
		
		
		protected void map(Object key, Object value, Context context) 
				throws IOException, InterruptedException {
			
			String[] tokens = value.toString().trim().split(","); 
			if(tokens!=null && tokens.length==2) {
				if(table.equals("address.dat")) {
					out_key.set(tokens[0]);
					out_value.set("2," + value);
					context.write(out_key, out_value);
				}
				else if(table.equals("factory.dat")) {
					out_key.set(tokens[1]);
					out_value.set("1," + value);
					context.write(out_key, out_value);
				}
			}
		}
	}
	static class MyReduce extends Reducer <Text, Text, Text, Text> {
		 
		 Text out_key = new Text();
		 Text out_value = new Text();
		 protected void reduce(Text key, Iterable<Text> values, Context context)
				throws IOException, InterruptedException {
			  
			 List<String> factory = new ArrayList<String>(); 
			 List<String> address = new ArrayList<String>();
			 
			 for(Text t:values) {
				 String[] tokens = t.toString().trim().split(",");
				 if(tokens[0].equals("1")) {
					 address.add(tokens[1]);
				 }
				 else if(tokens[0].equals("2")) {
					 factory.add(tokens[2]);
				 }
			 }
			 
			 for(String k:address) {
				 for(String v:factory) {
					 out_key.set(k);
					 out_value.set(v);
					 context.write(out_key, out_value);
				 }
			 }
		 }     	
	}

三、倒排索引

"倒排索引"是文档检索系统中最常用的数据结构,被广泛地应用于全文搜索引擎。它主要是用来存储某个单词(或词组)在一个文档或一组文档中的存储位置的映射,即提供了一种根据内容来查找文档的方式。由于不是根据文档来确定文档所包含的内容,而是进行相反的操作,因而称为倒排索引(Inverted Index)。

3.1实例描述

通常情况下,倒排索引由一个单词(或词组)以及相关的文档列表组成,文档列表中的文档或者是标识文档的ID号,或者是指文档所在位置的URL。
在实际应用中,还需要给每个文档添加一个权值,用来指出每个文档与搜索内容的相关度

输入:

1)file1:  
MapReduce is simple
2)file2:  
MapReduce is powerful is simple 
3)file3:  
Hello MapReduce bye MapReduce

输出

MapReduce      file1.txt:1;file2.txt:1;file3.txt:2;
is            file1.txt:1;file2.txt:2;
simple           file1.txt:1;file2.txt:1;
powerful      file2.txt:1;
Hello          file3.txt:1;
bye            file3.txt:1;

3.2 问题分析

实现"倒排索引"只要关注的信息为:单词、文档URL及词频。但是在实现过程中,索引文件的格式会略有所不同,以避免重写OutPutFormat类

3.3实现步骤

1)Map过程
首先使用默认的TextInputFormat类对输入文件进行处理,得到文本中每行的偏移量及其内容。显然,Map过程首先必须分析输入的<key,value>对,得到倒排索引中需要的三个信息:单词、文档URL和词频
首先使用默认的TextInputFormat类对输入文件进行处理,得到文本中每行的偏移量及其内容。显然,Map过程首先必须分析输入的<key,value>对,得到倒排索引中需要的三个信息:单词、文档URL和词频

这里写图片描述

<key, value>设置为<文件:单词,   次数(此为1)>

存在两个问题:
第一,<key,value>对只能有两个值,在不使用Hadoop自定义数据类型的情况下,需要根据情况将其中两个值合并成一个值,作为key或value值;
第二,通过一个Reduce过程无法同时完成词频统计和生成文档列表,所以必须增加一个Combine过程完成词频统计。

单词和URL组成key值(如"MapReduce:file1.txt"),将词频作为value,这样做的好处是可以利用MapReduce框架自带的Map端排序,将同一文档的相同单词的词频组成列表,传递给Combine过程,实现类似于WordCount的功能。

2)Combine过程

经过map方法处理后,Combine过程将key值相同的value值累加,得到一个单词在在文档中的词频,如果直接将图所示的输出作为Reduce过程的输入,在Shuffle过程时将面临一个问题:所有具有相同单词的记录(由单词、URL和词频组成)应该交由同一个Reducer处理,但当前的key值无法保证这一点,所以必须修改key值和value值。
这次将单词作为key值,URL和词频组成value值(如"file1.txt:1")。可以利用MapReduce框架默认的HashPartitioner类完成Shuffle过程,将相同单词的所有记录发送给同一个Reducer进行处理

这里写图片描述

3)Reduce过程

经过上述两个过程后,Reduce过程只需将相同key值的value值组合成倒排索引文件所需的格式即可,剩下的事情就可以直接交给MapReduce框架进行处理了

4)需要解决的问题

本倒排索引在文件数目上没有限制,但是单词文件不宜过大(具体值与默认HDFS块大小及相关配置有关),要保证每个文件对应一个split。否则,由于Reduce过程没有进一步统计词频,最终结果可能会出现词频未统计完全的单词。可以通过重写InputFormat类将每个文件为一个split,避免上述情况。或者执行两次MapReduce,第一次MapReduce用于统计词频,第二次MapReduce用于生成倒排索引。除此之外,还可以利用复合键值对等实现包含更多信息的倒排索引。
这里写图片描述

3.4代码

static class MyMapper extends Mapper <Object, Object, Text, Text>{
		Text out_key = new Text();
		Text out_value = new Text();
		String table = "";
		protected void setup(Context context)
				throws java.io.IOException, java.lang.InterruptedException {
			FileSplit fs = (FileSplit) context.getInputSplit();
			table = fs.getPath().getName();
		}
	    
		protected void map(Object key, Object value, Context context) 
				throws IOException, InterruptedException {
			
			String[] tokens = value.toString().split(" "); 
			if(tokens!=null) {
				out_value.set("1");
				for(String s:tokens) {
					out_key.set(table + ":" + s);
					context.write(out_key, out_value);
				}
			}
		}
	}
	
	static class MyCombiner extends Reducer <Text, Text, Text, Text>{
		Text out_key = new Text();
		Text out_value = new Text();
		
		protected void reduce(Text key, Iterable<Text> values, Context context)
				throws IOException, InterruptedException {
			 
			//file1:word1 2    ->    word1  file1:2
			//file2:word1 5    ->    word1  file2:5
			String[] tokens = key.toString().split(":");
			if(tokens!=null && tokens.length==2){
				int k = 0;
				for(Text val:values){
					k += Integer.parseInt(val.toString());
				}
				
				out_key.set(tokens[1]);
				out_value.set(tokens[0] + ":" + Integer.toString(k));
				context.write(out_key, out_value);
			}
		 }   
	}
	
	static class MyReduce extends Reducer <Text, Text, Text, Text> {
		 
		 Text out_key = new Text();
		 Text out_value = new Text();
		 protected void reduce(Text key, Iterable<Text> values, Context context)
				throws IOException, InterruptedException {
			 
			 String s = ""; 
			 for(Text t:values) {
				 s += t.toString() + ";";
			 }
			 out_key.set(key);
			 out_value.set(s);
			 context.write(out_key, out_value);
		 }     	
	}

四、TF-IDF

原理:在一个文件里,词频(Term Frequency)TF指某个词在文件中出现的次数
这个数字通常会被正规化,防止它偏向长的文件(在长文件中比短文件有更高的词频,而不管该词是否重要)
IDF逆向文件频率是普遍重要度的度量
IDF=Math.log10(总文件数/包含该词文件的数目) 。

某一文件内的高词频,以及该词在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此TF-IDF可以过滤掉常见的词语,保留重要的词语

例子:
在一篇文件总词数100个,其中mapreduce出现3次,词频=0.03
测定mapreduce在1000份文件中出现过,文件总数10000000,IDF=log(10000000/1000)=4
最后 TF-IDF的分数=0.03*4=0.12

4.1问题分析

  1. job1 对每个文件集中每个输入文件,分别统计其各个单词出现次数,输出为
    <单词w |文件名f , w在f中出现的次数 f-w-count>

  2. job2 对job1的输出,统计文件f中所有单词的个数(及一共有多少个唯一的单词)
    输出为 <单词w | 文件名f,w在f中出现次数 f-w-count | 文件f中单词数f-length>

  3. job3 先统计文件集的文件个数length;然后,根据job2的输出,统计每个单词在所有文件集中出现的文件个数,输出

    1. <w,[f1=f1-w-count|f1-length, f2… ]>
    2. <w|f1,f1-w-count|f1-length*log(length/k)>
      即<单词 w|文件名f1, tf-idf-f1-w>也就是每个单词在文件中的权重TF-IDF。
      统计文件集docs个数length
      +使用log(length/f-contains-w)=IDF
      +f-w-count/length得到TF

使用另一种思路方法:
map阶段
1.setup阶段进行文件总数的统计。
2.map阶段进行单个文件中,每个单词出现的次数,以及文件中不一样的单词总数的计算。
3.cleanup阶段对context进行写入,key为word,以方便reduce阶段根据word进行分组,得到word一共出现在几个文件中。

	static int file_num=0;   //文件总数
	static String INPUT = "hdfs://master:9000/back_sort";
	static String OUTPUT = "hdfs://master:9000/output";

	static class MyMapper1 extends Mapper <Object, Object, Text, Text>{
		Text out_key = new Text();
		Text out_value = new Text();
		String table = "";
		Map <String, String> wordmap = new HashMap<String,String>();  //存储<单词,数量>
		protected void setup(Context context)
				throws java.io.IOException,
				java.lang.InterruptedException {
			FileSplit fs = (FileSplit) context.getInputSplit();
			table = fs.getPath().getName(); //文件名
			file_num++;
		}
	    
		protected void map(Object key, Object value, Context context) 
				throws IOException, InterruptedException {
			
			String[] tokens = value.toString().split(" ");
			if(tokens!=null) {
				for(String token:tokens){
					if(wordmap.get(token)!=null) {//更新wordmap
						wordmap.replace(token, wordmap.get(token), String.valueOf(Integer.parseInt(wordmap.get(token))+1));
					} else {
						wordmap.put(token, "1");
					}
				}
			}
			
		}

		protected void cleanup(Context context)
				throws java.io.IOException, java.lang.InterruptedException {
			int word_count_all = wordmap.size();
			for (Map.Entry<String, String> entry : wordmap.entrySet()) { 
				  out_key.set(entry.getKey());			//word				//word_count_all
				  out_value.set(table + "," + entry.getValue() + "," +word_count_all);		//word_count		//table	
				  context.write(out_key, out_value);
			}
		}
	}

Reduce阶段

1.得到数据后,遍历values得到单词出现过的文件数量,将其保存在List中,然后遍历List,取出数据,并计算TF-IDF
2.context.write()

static class MyReduce1 extends Reducer <Text, Text, Text, Text>{
		Text out_key = new Text();
		Text out_value = new Text();
		Double count;
		Double count_all;
		Double TF;
		Double IDF;
		Double TF_IDF;
		
		protected void reduce(Text key, Iterable<Text> values, Context context)
				throws IOException, InterruptedException {
			List<String> mid = new ArrayList<String>();
//			word,file    1
//			word     {table count count_all, table count count_all}
			int files_include_num = 0;
			for(Text val:values) {
				mid.add(val.toString());
				files_include_num++;
			}
			out_key.set(key);
			String valu = "";
			for(String e:mid) {
				String[] tokens = e.split(",");
				if(tokens!=null && tokens.length==3) {
					count = Double.parseDouble(tokens[1]);
					count_all = Double.parseDouble(tokens[2]);
					double a = file_num;
					double b = files_include_num;
					TF = count/count_all;
					IDF = Math.log10(a/b);
					TF_IDF = TF*IDF;
					valu += tokens[0] + ":" + TF_IDF + "; ";
				}
			}
			out_value.set(valu);
			context.write(out_key, out_value);
		 }   
	}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值