简介
2、相对频度
这里。我们来研究一个简单的例子。通过计算一个给定文档集中单词的相对频度来展示OI模式。这里的目标是建立一个N*N矩阵(M)。其中
N = ∣ V ∣ (所有给定文档的单词量) N = |V|(所有给定文档的单词量) N=∣V∣(所有给定文档的单词量)
每个单元 M i j M_{ij} Mij包含一个特定上下文单词 W i W_{i} Wi与单词 W j W_{j} Wj共同出现的次数。为简单起见,我们将这个上下文定义为 W i W_{i} Wi的邻域。例如,给定下面的单词:
W 1 , W 2 , W 3 , W 4 , W 5 , W 6 W_{1},W_{2},W_{3},W_{4},W_{5},W_{6} W1,W2,W3,W4,W5,W6
如果定义一个单词的邻域为这个单词的前两个单词和后两个单词(现实情况前1后1表现就已经很好),那么这6个单词的邻域如下表所示:
单词 | 邻域 |
---|---|
W 1 W_{1} W1 | W 2 , W 3 W_{2},W_{3} W2,W3 |
W 2 W_{2} W2 | W 1 , W 3 , W 4 W_{1},W_{3},W_{4} W1,W3,W4 |
W 3 W_{3} W3 | W 1 , W 2 , W 4 , W 5 W_{1},W_{2},W_{4},W_{5} W1,W2,W4,W5 |
W 4 W_{4} W4 | W 2 , W 3 , W 5 , W 6 W_{2},W_{3},W_{5},W_{6} W2,W3,W5,W6 |
W 5 W_{5} W5 | W 3 , W 4 , W 6 W_{3},W_{4},W_{6} W3,W4,W6 |
W 6 W_{6} W6 | W 4 , W 5 W_{4},W_{5} W4,W5 |
对于这个例子,计算相对频度需要得到边缘计数,也就是行和列总数。不过,在得到所有计数之前,将无法计算边缘计数。
因此,要让边缘计数在联合计数之前到达归约器。
我们不使用绝对计数,而使用相对频度来描绘单词的特性。也就是说,在 W i W_{i} Wi的上下文中 W j W_{j} Wj出现的比例有多大。如下公式所示:
f ( W j ∣ W i ) = N ( W i , W j ) ∑ w N ( W i , w ) f(W_{j}|W_{i}) = \frac{N(W_{i},W_{j})}{\sum_{w} N(W_{i},w) } f(Wj∣Wi)=∑wN(Wi,w)N(Wi,Wj)
在这里, N ( a , b ) N(a,b) N(a,b)表示语料库(作为输入的所有文档的一个集合)中观察到的一个特定共现单词对出现的次数。因此,我们要得到这个联合事件(单词共现)的次数,除以边缘计数(条件变量与其他单词共现的次数的总和)。
3、MapReduce实现
3.1、定制分区器
该分区器将包含相同左词的所有词发送到同一个归约器。例如组合键
{(man,tail),(man,strong),(man,moon),...,(man love)}
都要发送到同一个归约器,要把这些键发送到相同的归约器,必须定义一个定制分区器,他只关心左词的散列。以此为标准完成分区。如下代码所示:
java
package com.sunrun.movieshow.autils.oi;
import edu.umd.cloud9.io.pair.PairOfStrings;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.mapreduce.Partitioner;
public class OrderInversionPartitioner extends Partitioner<PairOfStrings, IntWritable> {
@Override
public int getPartition(PairOfStrings key, IntWritable value, int numberOfPartitions) {
// key = left,right word
String leftWord = key.getLeftElement();
return (int)Math.abs(hash(leftWord) % numberOfPartitions);
}
private static long hash(String word){
// 质数
long h = 1125899906842597L;
int length = word.length();
for (int i = 0; i < length; i++) {
h = 31 * h + word.charAt(i);
}
return h;
}
}
3.2、频度映射器
这个类生成一个单词邻域的相对频度,例如,如果一个mapper接收到如下的输入
w1 w2 w3 w4 w5 w6
那么,他会发出如下所示的键值对
(w1,w2) 1
(w1,w3) 1
(w1,*) 2 // 包含w1的词对总数
(w2,w1) 1
(w2,w3) 1
(w2,w4) 1
(w2,*) 3
(w3,w1) 1
(w3,w2) 1
(w3,w4) 1
(w3,w5) 1
(w3,*) 4
...
(w6,w4) 1
(w6,w5) 1
(w6,*) 1`
mapper的算法如下所示:
java
package com.sunrun.movieshow.autils.oi;
import edu.umd.cloud9.io.pair.PairOfStrings;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.io.StringWriter;
public class RelativeFrequencyMapper extends Mapper<LongWritable, Text, PairWord,LongWritable> {
// 窗口大小配置
private int neighborWindow;
private PairWord pair = new PairWord();
// 每次写入的对象,节省开销
private final LongWritable totalCount = new LongWritable();
private static final LongWritable ONE_COUNT = new LongWritable();
@Override
protected void setup(Context context) throws IOException, InterruptedException {
// 驱动器将设置"neighbor.window",读取窗口大小配置
neighborWindow = context.getConfiguration().getInt("neighbor.window",2);
}
/**
*
* @param key 行号
* @param value 单词集
* @param context 上下文对象
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] tokens = value.toString().split(" ");
if(tokens.length < 2){
return;
}
for (int i = 0; i < tokens.length; i++) {
String word = tokens[i];
pair.setLeftElement(word);
// 获取当前当次的邻域索引,进行访问
int start = (i - neighborWindow < 0) ? 0 : i - neighborWindow;
int end = (i + neighborWindow > tokens.length) ? tokens.length - 1: i + neighborWindow;
for (int j = start; j < end; j++) {
// 邻域不包括自身
if( i == j){
continue;
}
// 写入右词,并删除多余的空格
pair.setRightElement(tokens[j].replaceAll("\\W",""));
context.write(pair, ONE_COUNT);
}
// 当前pair的邻域单词总数
pair.setRightElement("*");
totalCount.set(end - start);
context.write(pair, totalCount);
}
}
}
其中,我们定义了一个自定义传输类PairWord,在hadoop的序列化一章节我们有提到创建这种bean的步骤,可以前往本博客的hadoop第18节了解学习。这里参考算法书籍的设计模式,如下所示:
java
package com.sunrun.movieshow.autils.oi;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* 参考PairOfStrings类进行设计,大数据工具类都需要实现WritableComparable接口
*/
public class PairWord implements WritableComparable<PairWord> {
private String leftElement;
private String rightElement;
// == constructor
public PairWord() {
}
public PairWord(String leftElement, String rightElement) {
set(leftElement, rightElement);
}
// == getter and setter
public String getLeftElement() {
return leftElement;
}
public void setLeftElement(String leftElement) {
this.leftElement = leftElement;
}
public String getRightElement() {
return rightElement;
}
public void setRightElement(String rightElement) {
this.rightElement = rightElement;
}
/**
* set 方法
* @param left
* @param right
*/
public void set(String left, String right) {
leftElement = left;
rightElement = right;
}
// == 序列化反序列化(Writable接口)
/**
* 序列化
* @param out
* @throws IOException
*/
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(leftElement);
out.writeUTF(rightElement);
}
/**
* 反序列化,注意顺序和序列化的顺序保持一致
* @param in
* @throws IOException
*/
@Override
public void readFields(DataInput in) throws IOException {
leftElement = in.readUTF();
rightElement = in.readUTF();
}
// == 实现结构 java.lang.comparable,和序列化接口加起来,刚好是WritableComparable的内容
/**
* 左词优先排序
* @param pairWord
* @return
*/
@Override
public int compareTo(PairWord pairWord) {
String pl = pairWord.getLeftElement();
String pr = pairWord.getRightElement();
if (leftElement.equals(pl)) {
return rightElement.compareTo(pr);
}
return leftElement.compareTo(pl);
}
// == 检查两个pair是否相等
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
if(!(obj instanceof PairWord)){
return false;
}
PairWord other = (PairWord) obj;
return leftElement.equals(other.getLeftElement()) && rightElement.equals(other.getRightElement());
}
// == hashcode生成
@Override
public int hashCode() {
return leftElement.hashCode() + rightElement.hashCode();
}
// == toString 方法(关系到最后的文件输出)
@Override
public String toString() {
return "(" + leftElement + "," + rightElement + ")";
}
// == 克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
return new PairWord(this.leftElement,this.rightElement);
}
}
3.3、频度归约器
既然已经有了定制分区器和OI模式的实现,归约器接受的值将基于现有映射器生成的键的自然键。接下来要做的,就是在归约器实现中综合结果:
java
package com.sunrun.movieshow.autils.oi;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class RelativeFrequencyReducer extends Reducer<PairWord, LongWritable,PairWord, DoubleWritable> {
// 当前处理的词汇,注意保证初始值不可能在语料库中出现;
private String currentWord = "NOT_DEFINED";
// 统计当前出现的次数
private double totalCount = 0;
private final DoubleWritable relativeCount = new DoubleWritable();
@Override
protected void reduce(PairWord key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
// 如果右词是*,则
if(key.getRightElement().equals("*")){
if(key.getLeftElement().equals(currentWord)){
totalCount += getTotalCount(values);
}else{
currentWord = key.getLeftElement();
totalCount = getTotalCount(values);
}
}else{
int count = getTotalCount(values);
relativeCount.set((double) count / totalCount);
context.write(key, relativeCount);
}
}
private int getTotalCount(Iterable<LongWritable> values) {
int sum = 0;
for (LongWritable value : values) {
sum += value.get();
}
return sum;
}
}
最后,使用Driver驱动整个程序
java
package com.sunrun.movieshow.autils.oi;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
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.output.FileOutputFormat;
import java.util.UUID;
/**
* 逆转排序,实现单词频度计算。
*/
public class OIDriver {
public static void main(String[] args) throws Exception {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(OIDriver.class);
job.setMapperClass(RelativeFrequencyMapper.class);
job.setReducerClass(RelativeFrequencyReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
job.setOutputValueClass(PairWord.class);
job.setOutputKeyClass(DoubleWritable.class);
job.setPartitionerClass(OrderInversionPartitioner.class);
//job.setNumReduceTasks(4);
// add your group comparator class.
//job.setGroupingComparatorClass(TemperatureGroupComparator.class);
FileInputFormat.setInputPaths(job,new Path("words.txt"));
FileOutputFormat.setOutputPath(job,new Path("output_" + UUID.randomUUID()) );
System.exit(job.waitForCompletion(true)? 1:0);
}
}