基于MapReduce的词频统计及简单的情感分析

Neo
Neo
2020-09-20 / 0 评论 / 311 阅读

这篇文章介绍一下怎样用MapReduce对文本进行词频统计,并基于统计好的词频文件进行简单的情感分析。

文本概述

文本来源于微博热门事件的评论,大概有1.5万条评论数据,文本的获得可以参考我前面的文章(python爬取微博事件评论),对于MapReduce来说,这个文本量实在是有些大材小用,不过再小的蚊子也是肉,拿来练手还是可以的。
这个文本长啥样?在下面!

懂王已经疯了
退朗普是一天天不作会死嘛,自己国家啥样,心里没点AC数呀
腾讯准备在南山起诉美国
连锁反应来了 字节下跪成功开了个头
估计尝到甜头了,越来越过分
这是为了维持美国的信息传播霸权,控住了信息传播就可以控制普通民众的思维方式,世界上除了中国,基本信息传播都牢牢掌控在美国手里,现在中国的不受美国控制的信息传播方式逐渐渗入世界各个地区,这是美国霸权决不允许存在的。
让不让留学生活了…………
特朗普需不需要精神检测一下
自由美利坚 枪战每一天
总感觉这是特普朗的为了连任的策略,把美国搅成一锅粥,怎么乱怎么来,让后面的无法接盘
牛逼人家说禁用禁用,苹果手机我们做得到?果咀总有千万种理由去舔
TT从500亿美国业务到300亿全球业务再加1600亿的索赔金额用了多久?丹麦都不敢想
坐看腾讯操作
总统当成国王了。
霸权主义!
特朗普每次都给自己留足了反悔的时间
懂王已经疯了
退朗普是一天天不作会死嘛,自己国家啥样,心里没点AC数呀
腾讯准备在南山起诉美国
连锁反应来了 字节下跪成功开了个头
估计尝到甜头了,越来越过分
这是为了维持美国的信息传播霸权,控住了信息传播就可以控制普通民众的思维方式,世界上除了中国,基本信息传播都牢牢掌控在美国手里,现在中国的不受美国控制的信息传播方式逐渐渗入世界各个地区,这是美国霸权决不允许存在的。
让不让留学生活了…………
特朗普需不需要精神检测一下
自由美利坚 枪战每一天
总感觉这是特普朗的为了连任的策略,把美国搅成一锅粥,怎么乱怎么来,让后面的无法接盘
牛逼人家说禁用禁用,苹果手机我们做得到?果咀总有千万种理由去舔
TT从500亿美国业务到300亿全球业务再加1600亿的索赔金额用了多久?丹麦都不敢想

MapReduce处理文本的过程

MapReduce的全过程还是比较复杂的,如下图所示。

但是整个过程的大部分工作已经由框架自动完成,用户具体需要的编码过程并不多,对于词频统计来说,只要实现具体的map和reduce的过程就行了。

Map过程

    /**
     * Object:输入< key, value >对的 key 值,此处为文本数据的起始位置的偏移量。在大部分程序下这个参数可以直接使用 Long 类型,源码此处使用Object做了泛化
     * Text:输入< key, value >对的 value 值,此处为一段具体的文本数据
     * Text:输出< key, value >对的 key 值,此处为一个单词
     * IntWritable:输出< key, value >对的 value 值,此处固定为 1 。IntWritable 是 Hadoop 对 Integer 的进一步封装,使其可以进行序列化。
     */
    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
        //one:类型为Hadoop定义的 IntWritable 类型,其本质就是序列化的 Integer ,one 变量的值恒为 1
        private final static IntWritable one = new IntWritable(1);
        //word:因为在WordCount程序中,Map端的任务是对输入数据按照单词进行切分,每个单词为Text类型
        private Text word = new Text();

        /**
         * @param key:输入数据在原数据中的偏移量
         * @param value:具体的数据,此处为一段字符串
         * @param context:用于暂时存储 map() 处理后的结果
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            //创建一个jieba分词器
            JiebaSegmenter jiebaSegmenter = new JiebaSegmenter();
            //定义一个正则表达式,用来过滤掉非中文字符
            String regEx2 = "[^\u4E00-\u9FA5]";
            //对该行文本进行过滤
            String valueNew = value.toString().replaceAll(regEx2,"");
            //对文本进行分词
            ListIterator<String> listIterator = jiebaSegmenter.sentenceProcess(valueNew).listIterator();
            //将分词文件迭代写入到context中
            while (listIterator.hasNext()){
                word.set(listIterator.next());
                context.write(word, one);
            }
        }
    }

Reduce过程

    /**
     * Text:输入< key, value >对的key值,此处为一个单词
     * IntWritable:输入< key, value >对的value值
     * Text:输出< key, value >对的key值,此处为一个单词
     * IntWritable:输出< key, value >对,此处为相同单词词频累加之后的值。实际上就是一个数字
     */
    public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        private IntWritable result = new IntWritable();

        /**
         * @param key:输入< key, value >对的key值,也就是一个单词
         * @param values:一系列key值相同的序列化结构
         * @param context:临时存储reduce端产生的结果
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            //定义一个sum用来临时存储单词的频次
            int sum = 0;
            //reduce过程
            for (IntWritable val : values) {
                sum += val.get();
            }
            result.set(sum);
            //reduce结果
            context.write(key, result);
        }
    }

词频统计部分结果如下

一	89
一一	3
一万步	1
一上	2
一上台	1
一下	198
一下一下	1
一下下	1
一下子	3
一不小心	1
一丘之貉	1
一丝	1
一丝一毫	2
一两个	1
一个	467
一个个	10
一个劲	2
一个半月	1
一个头	3
一个打	1
一个整	1
一个月	10
一个样	2
一串	2
一举一动	1
一了百了	2
一二三	1
一些	40

词云可视化

词云能够很好的表达一个词频文件的中的主体,其基本原理是通过一张图片直观的展示词频文件中的高频词汇,词汇的频率越高其字体越大,以突显主体。本文基于java库KUMO来实现词云的输出,其主要配置文件如下。

    public static void wordCloud(HashMap<String, String[]> hashMap){

        //拿到文档里面分出的词,和词频,建立一个集合存储起来
        List<WordFrequency> wordFrequencies = new ArrayList<>();
        for(String[] v:hashMap.values()){
            if(v[0].length()>1)
                wordFrequencies.add(new WordFrequency(v[0],Integer.parseInt(v[1])));
        }

        //设置图片相关的属性,这边是大小和形状,更多的形状属性,可以从CollisionMode源码里面查找
        WordCloud wordCloud = new WordCloud(new Dimension(1000, 1000), CollisionMode.PIXEL_PERFECT);
        //设置词云边界
        wordCloud.setPadding(10);

        //这边要注意意思,是设置中文字体的,如果不设置,得到的将会是乱码,
        //这是官方给出的代码没有写的,我这边拓展写一下,字体,大小可以设置
        //具体可以参照Font源码
        java.awt.Font font = new java.awt.Font("STSong-Light", 2, 20);
        wordCloud.setKumoFont(new KumoFont(font));
        //设置背景颜色
        wordCloud.setBackgroundColor(new Color(255, 255, 255));
        //设置背景形状
//        wordCloud.setBackground(new CircleBackground(255));
        //设置字体颜色
        wordCloud.setColorPalette(new LinearGradientColorPalette(Color.RED, Color.BLUE, Color.GREEN, 30, 30));
        //设置字体大小
        wordCloud.setFontScalar(new SqrtFontScalar(12, 100));
        //将文字写入图片
        wordCloud.build(wordFrequencies);
        //生成图片
        wordCloud.writeToFile("weibo/chinese_language_circle.png");
    }

词云
基于词频文件,过滤到其中的低频词以及无意义的普通字,生成如下图所示的词云图,图中字体越大代表其频率越高。
image.png
通过该图能够清楚的看到这次事件的关联主体:中国、美国、特朗普、微信,次关联的主体包括:腾讯、苹果、华为、字节等。

用户评论的情感倾向分析

在本文中,该事件评论的整体情感倾向是通过所得的词频文件中每一个单词的情感倾向的累计求和。其中每一个单词的情感倾向可以通过标注好情感的词库来完成,本实验拟定采用知网提供的词库BosonNLP_sentiment_score来查询,其拥有超过11万的词汇,基本能够满足本实验所需。相关的处理算法如下。

    public static void main(String[] args) {
        File file = new File("weibo/001.txt");
        HashMap<String, String[]> words = readtxt(file,"\t");

        wordCloud(words);

        File file2 = new File("weibo/BosonNLP_sentiment_score.txt");
        HashMap<String, String[]> sentiments = readtxt(file2," ");

        int pos_num = 0;
        int neg_num = 0;
        float pos_feeling = 0;
        float neg_feeling = 0;
        float feeling = 0;

        for(String[] v:words.values()){
            if(sentiments.containsKey(v[0])){
                String[] feelingWord = sentiments.get(v[0]);

                if (Float.parseFloat(feelingWord[1]) >= 0)
                {
                    pos_num+=Integer.parseInt(v[1]);
                    pos_feeling += Float.parseFloat(feelingWord[1])*Integer.parseInt(v[1]);

                } else {
                    neg_num+=Integer.parseInt(v[1]);
                    neg_feeling += Float.parseFloat(feelingWord[1])*Integer.parseInt(v[1]);
                }
                feeling = pos_feeling+neg_feeling;
            }
        }

        System.out.println(pos_num);
        System.out.println(pos_feeling);
        System.out.println(neg_num);
        System.out.println(neg_feeling);
        System.out.println(feeling);
        System.out.println(Math.abs(pos_feeling)/(Math.abs(pos_feeling)+Math.abs(neg_feeling)));
    }

情感分析结果
对用户评论词进行分析,积极词汇有61541个,积极词汇的总评分为42405.805,消极词汇有59672个,消极词汇的总评分为45813.402。通过如下公式,最总得出整体情感倾向为0.48,即针对该事件的评论,整体是带有着一点负面情绪。

源码

基于MapReduce的词频统计及简单的情感分析