基于hadoop搜索引擎实践——生成倒排表文件(三)

本文介绍了一种处理BBS论坛帖子的方法,包括源文件的预处理与过滤、生成倒排索引的过程。详细说明了如何利用改进的TF-IDF算法计算索引词的相关度(Rank)以及记录索引词的位置(Position),以便后续检索时生成相关性排序和摘要。
1.源文件过滤
    在对源文件进行功能性处理之前,有必要对生成的源文件进行一次预分析和过滤。
    (1)去重,过滤掉爬取过程中重复的帖子,保持帖子的唯一性。
    (2)过滤不符合要求的帖子,比如获取的信息不能正常转为json格式的数据。内容全部为空的数据等。
    这部分过滤处理相对简单,在map阶段,把帖子的url作为key,map中的value仍为value,组成<key,value>传输到reduce中。在reduce接段,同一个key,只输出一次。代码如下:
    
public static class RemoveRepeatMapper extends Mapper<LongWritable, Text, Text, Text>{
        private static Gson gson=new Gson();
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            BBS bbs=gson.fromJson(value.toString(), BBS.class);
            context.write(new Text(bbs.getUrl()), value);
        }
    }
   
    public static class RemoveRepeatReducer extends Reducer<Text, Text, NullWritable, Text>{

        @Override
        protected void reduce(Text key, Iterable<Text> values,
                Context context)
                throws IOException, InterruptedException {
            while(values.iterator().hasNext()){
                context.write(NullWritable.get(), values.iterator().next());
                break;
            }
        }
    }

2.生成倒排索引文件
    倒排文件使用文档中所含有的关键词作为索引,把文档作为索引目标的一种结构(类似于有些纸质书籍的索引附录中,用关键词做索引,书的页面是索引目标)。这部分工作中是整个系统的重点,大致工作过程有如下3步。
    <1>对已过滤的源文件中的每条记录进行切分,并将每条记录中的title和content转化为一组词的集合。
    <2>为了在后期能够对用户查询的结果进行排序并显示摘要,在对帖子分词过程中,还需要计算出每个索引词对该帖子的相关度(Rank),以及在该帖子中出现的位置(Position).
    <3>在完成上述的分析和准备工作之后,设计MapReduce算法,把帖子和索引词之间的映射转化为索引词到帖子的映射,并在此过程中计算并统计索引词相关帖子的Rank和Position,由此生成的文件成为倒排表。
    (1)分词
    获取的帖子内容可能是中文和英文,在英文中是通过空格分隔,而在中文中的词汇大多数由两个或者两个以上的汉字组成,并且语句是连续书写的。在对中文文本进行自动分析前,需要将整句切割成小的词汇但愿,即中文分词(或中文切词),被系统采用开源分词软件IKAnalyzer.经过分词处理,一片帖子切分出来的有效的词语数量大约在及时到几百不等。
    (2)索引词的Rank和position
    计算并统计索引词相对于帖子的相关度(Rank)的目的是能够排序显示用户查询到结果帖子;记录索引词在该帖子中的位置(Posistion)用于显示查询结果时,能够生成该词在帖子中的部分摘要。
    关于Rank的计算有Google著名的PageRank算法,该算法的主要思路是越链接的次数越多的网页,其重要程度和知名程度越高,相应的Rank值也就越高。而本系统是一条条帖子,这些帖子基本都是独立的,且帖子与帖子之间几乎不存在网页上的链接关系。因此本系统并没有采用PageRank算法,而是针对自身的特点采用改进的TF-IDF算法用于计算Rank值。
    TF-IDF(Term Frequency-Inverse Document Frequency)算法的大致思想有亮点。第一,如果一个词在某个文档中出现的频率越高,那么这个词语与这边文档的相关度(rank)也就越高;第二,如果一个词在越多的文档中出现,则该词用于区分文档相关性的作用也就越小。TF-IDF算法的公式描述如下

式中,式(1)中的表示词t在文档n中出现的次数,表示文档n中词的总个数;式(2)中的|D|表示库中文档的总数目;表示包含词t的文档的数据;式总的是指词t与文档n的相关度

    本系统改进的部分是将词出现在文档的区域(标题或者正文)加入计算Rank的考虑范畴,改进后的算法思想描述如下:

    (1)令词i与帖子j的相关度表示为R,这个值越大表示越相关,初始为0

    (2)统计词i在帖子j的标题中出现的次数,每次出现一次,则R的值增加5

    (3)统计词i在帖子j的正文中出现的次数,每次出现一次,则R的值加1

    (4)在所有帖子中统计出包含词i的帖子的数据,记为num

    (5)最后计算出R的值等于R/num

相对于Rank的计算,记录Position则要容易得多,下面对此进行简单的介绍。

    IKAnalzer分词软件在切分出每个词时,同时会输出这个词在帖子中出现的起始和结束为止(相对于帖子首部的偏移量)。因此,Position信息只要用两个整数表示即可,本系统用(start,end)来表示。

    由于在MapReduce的计算过程中,需要在Map和Reduce阶段之间传递Rank和Position,因此本系统在将Rank和Position封装成类之后,继承了Hadoop提供的IO类中的Writable类,重新实现了一个Writable类RecordWritable ,用于封装并序列化传输Rank和Position的信息。类RecordWritable 的核心实现代码如下:

public class RecordWritable implements WritableComparable<RecordWritable> {

    private LongWritable DID = new LongWritable();
    private FloatWritable rank = new FloatWritable();
    private Text positions = new Text();

    public RecordWritable() {
    }

    public RecordWritable(LongWritable DID, FloatWritable rank, Text positions) {
        set(DID, rank, positions);
    }

    public void set(LongWritable DID, FloatWritable rank, Text positions) {
        this.DID.set(Long.valueOf(DID.toString()).longValue());
        this.rank.set(Float.valueOf(rank.toString()).floatValue());
        this.positions.set(positions.toString());
    }

    public void set(long DID, float rank, String positions) {
        this.DID.set(DID);
        this.rank.set(rank);
        this.positions.set(positions);
    }

    public void set(long DID, RankPosition rankPosition) {
        this.DID.set(DID);
        this.rank.set(rankPosition.getRank());
        this.positions.set(rankPosition.getPositions());
    }

    public void setDID(long DID) {
        this.DID.set(DID);
    }

    public void setRank(float rank) {
        this.rank.set(rank);
    }

    public void setPositions(Text positions) {
        this.positions.set(positions);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        // TODO Auto-generated method stub
        this.DID.readFields(in);
        this.rank.readFields(in);
        this.positions.readFields(in);
    }

    @Override
    public void write(DataOutput out) throws IOException {
        // TODO Auto-generated method stub
        this.DID.write(out);
        this.rank.write(out);
        this.positions.write(out);
    }

    public LongWritable getDID() {
        return DID;
    }

    public FloatWritable getRank() {
        return rank;
    }

    public Text getPositions() {
        return positions;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof RecordWritable) {
            RecordWritable tmp = (RecordWritable) obj;
            return this.DID.equals(tmp.DID) && this.rank.equals(tmp.rank)
                    && this.positions.equals(tmp.positions);
        }
        return false;

    }

    @Override
    public String toString() {
        return this.DID.toString() + ":" + this.rank.toString() + ":"
                + this.positions.toString();
    }

    @Override
    public int compareTo(RecordWritable tmp) {
        return this.DID.compareTo(tmp.DID);
    }
}

     具体实现代码可以查看:
参考文献:
1.刘鹏,hadoop实战,电子工业出版社,2011.9
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值