基于Hadoop平台下运用PMI指标的组合词判断

来源:互联网 发布:算法基础第五版 编辑:程序博客网 时间:2024/06/03 18:46

关于Hadoop的介绍

Hadoop是分布式文件系统(也就是HDFS),或者一个同类的分布式文件系统,管理着集群的数据。hadoop提供了一套基础设施来处理大多数困难的工作以保证任务能够执行成功。MapReduce是一种计算模型,该模型可将大型数据处理任务分解成很多单个的、可以在服务器集群中并行执行的任务。这些任务的计算结果可以合并在一起来计算最终的结果。

关于PMI的介绍

机器学习相关文献里面,经常会用到PMI(Pointwise Mutual Information)这个指标来衡量两个事物之间的相关性(比如两个词)。其原理很简单,公式如下: 

PMI(x;y)=logp(x,y)p(x)p(y)=logp(x|y)p(x)=logp(y|x)p(y)

在概率论中,我们知道,如果x跟y不相关,则p(x,y)=p(x)p(y)。二者相关性越大,则p(x,y)就相比于p(x)p(y)越大。用后面的式子可能更好理解,在y出现的情况下x出现的条件概率p(x|y)除以x本身出现的概率p(x),自然就表示x跟y的相关程度。 
这里的log来自于信息论的理论,可以简单理解为,当对p(x)取log之后就将一个概率转换为了信息量(要再乘以-1将其变为正数),以2为底时可以简单理解为用多少个bits可以表示这个变量。

原理分析

PMI在自然语言中可以用来计算一个词的情感倾向,但其实它也可以基于大数据判断一个词是否为组合词。
举个例子,大数据作为待判断的词,大可以作为x词,数据可以作为y词,而大数据则是作为xy词,N可以设定为一个常量,n(x),n(y),n(xy)分别为x,y,xy词出现的次数。那么可以得出PMI的一个近似计算公式:

PMI=N*n(xy)|x)n(y*)*n(x)

基于这个公式,我们可以设定一个阈值,大于这个值则判断为组合词。反之则不是。
那么基于以上原理以及大量数据,我们能够利用Hadoop平台判断一个词是否为组合词。

代码以及分析

public class WordDetect {public static HashSet<String> detectSet=new HashSet<String>();//用一个Key不重复的Set来记录要WordCount的词public static List<ThreeWordDetect> detectList=new ArrayList<ThreeWordDetect>();//用List保存用判断的三元组public static Map<String,Double> resultMap=new HashMap<String,Double>();//用Map来记录n(x|y|xy)的值//读取被处理文件的读取编码      static String fromFileCharset = "UTF-8";      //处理后文件的编码格式      static String toFileCharset = "UTF-8";       public static class DetectMap extends Mapper<Object, Text, Text, IntWritable>{ private final static IntWritable one = new IntWritable(1);    private Text word = new Text();    public void map(Object key, Text value, Context context) throws IOException, InterruptedException{        String line = value.toString();        Iterator<String> mapKey=detectSet.iterator();        for(int i=0;i<detectSet.size();i++){//检测每一行有没有要Count的词语        String regex=mapKey.next();        Pattern pattern = Pattern.compile(regex);    Matcher matcher = pattern.matcher(line);    while(matcher.find()){    word.set(regex);    context.write(word, one);    }        }        }}public static class DetectReducer extends Reducer<Text, IntWritable, Text, IntWritable>{ private IntWritable result = new IntWritable();    public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException{        int sum = 0;        for(IntWritable val :values){            sum+= val.get();        }        result.set(sum);        context.write(key, result);    }}/** * @param args * @throws IOException  * @throws InterruptedException  * @throws ClassNotFoundException  */public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {          InputStreamReader isReader = null;            FileInputStream fiStream = null;            BufferedReader bReader = null;            fiStream = new FileInputStream("/home/hadoop/桌面/待判断组合词");//读取待判断的组合词            isReader = new InputStreamReader(fiStream,fromFileCharset);            bReader = new BufferedReader(isReader);            String tempString;          while((tempString = bReader.readLine()) != null){         String[] group=tempString.split("\\s");         ThreeWordDetect word=new ThreeWordDetect()//设置三元组;         word.xWord=group[0];         word.yWord=group[1];         word.xyWord=group[2];         detectList.add(word);         for(int i=0;i<3;i++){         detectSet.add(group[i]);//设置Set的Key(设置要计数的词语)         }         }Configuration conf = new Configuration();String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();HdfsDAO hdfs=new HdfsDAO(conf);String Hdfs=HdfsDAO.getHdfs()+"user/hadoop/";//获取HDFS的路径Job job1 = new Job(conf, "WordDetect");if(otherArgs.length != 2){            System.err.println("Usage: wordcount<in> <out>");            System.exit(2);    }job1.setJarByClass(WordDetect.class);job1.setMapperClass(DetectMap.class);job1.setCombinerClass(DetectReducer.class);               job1.setReducerClass(DetectReducer.class);         job1.setOutputKeyClass(Text.class);        job1.setOutputValueClass(IntWritable.class);        FileInputFormat.addInputPath(job1, new Path(otherArgs[0]));           FileOutputFormat.setOutputPath(job1, new Path(otherArgs[1]));        job1.waitForCompletion(true); hdfs.download(Hdfs+otherArgs[1], "/home/hadoop/桌面/Result");//下载WordCount的结果        fiStream = new FileInputStream("/home/hadoop/桌面/Result/part-r-00000");//读取n(x|y|xy)的值isReader = new InputStreamReader(fiStream,fromFileCharset);  bReader = new BufferedReader(isReader);  while((tempString = bReader.readLine()) != null){String[] group=tempString.split("\\s");resultMap.put(group[0], Double.parseDouble(group[1]));//写入map中}File wFile = new File("/home/hadoop/桌面/finalResult");wFile.createNewFile();  OutputStreamWriter os = null;  FileOutputStream fos = null;  fos = new FileOutputStream("/home/hadoop/桌面/finalResult");  os = new OutputStreamWriter(fos,toFileCharset);for(int i=0;i<detectList.size();i++){        String requireWord=detectList.get(i).xyWord;        os.write(requireWord+" ");        double v=calculate(requireWord,detectList.get(i).xWord,detectList.get(i).yWord);//计算PMI值        os.write(v+"\n");            }                os.close();                bReader.close();}//计算PMI值的函数          private static double calculate(String requireWord, String xWord,String yWord) {double xSum=resultMap.get(xWord);double ySum=resultMap.get(yWord);double xySum=resultMap.get(requireWord);double result=(Double)(1000000*xySum)/(xSum*ySum);return result;  }}

我们的输入组合词文件件的格式是这样的,这个文件将会被记录到List中
大 数据 大数据数据 分析 数据分析产品 经理 产品经理

那么Set中便会保存以下元素
大,数据,大数据,分析,数据分析,产品,产品经理,经理
在经过一次MapReduce后,得到的结果将会以键值对的形式记录在Map当中
大 12345数据 48800大数据 328938分析 32889...........
然后将List中每一个Item的xWord,yWord,xyWord作为Key,我们会索引相应的Value,获得Value后进行计算便可以获得PMI值。
我们可以输入两组组合词训练数据,获得一组是已知组合词的PMI,获得另一组是已知非组合词的PMI。根据这两组数据决定阈值

反思与总结

首先时间是硬伤,实测100万篇博客数据跑55个组合词(记录入Set的约135个)需要30分钟。正则匹配那一部分所需要的时间过多,确实需要改进。其次,阈值的确定有点玄学,由于本人接触此方面知识时间不久,暂时没有什么好办法来确定阈值。欢迎提出建议或者指正错误。最后的话需要感谢师兄师姐给予的提点和帮助。