【基础算法】反转排序(文本相对频度)

本文介绍了如何使用MapReduce在大规模数据集上计算单词的相对频度,包括定制分区器、频度映射器和归约器的实现过程,以及如何通过这些组件计算文档中单词出现的上下文频率。
摘要由CSDN通过智能技术生成

简介

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(WjWi)=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)}

都要发送到同一个归约器,要把这些键发送到相同的归约器,必须定义一个定制分区器,他只关心左词的散列。以此为标准完成分区。如下代码所示:

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的算法如下所示:

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节了解学习。这里参考算法书籍的设计模式,如下所示:

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模式的实现,归约器接受的值将基于现有映射器生成的键的自然键。接下来要做的,就是在归约器实现中综合结果:

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驱动整个程序

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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值