MapReduce之金庸的江湖人物分析項(xiàng)目

項(xiàng)目設(shè)計(jì)目的

通過一個(gè)綜合數(shù)據(jù)分析案例:”金庸的江湖——金庸武俠小說中的人物關(guān)系挖掘“,來學(xué)習(xí)和掌握MapReduce程序設(shè)計(jì)。通過本項(xiàng)目的學(xué)習(xí),可以體會如何使用MapReduce完成一個(gè)綜合性的數(shù)據(jù)挖掘任務(wù),包括全流程的數(shù)據(jù)預(yù)處理、數(shù)據(jù)分析、數(shù)據(jù)后處理等。
1 任務(wù)1 數(shù)據(jù)預(yù)處理
1.1 任務(wù)描述
從原始的金庸小說文本中,抽取出與人物互動相關(guān)的數(shù)據(jù),而屏蔽掉與人物關(guān)系無關(guān)的文本內(nèi)容,為后面的基于人物共現(xiàn)的分析做準(zhǔn)備。

數(shù)據(jù)輸入:
1.全本的金庸武俠小說文集(未分詞);

  1. 金庸武俠小說人名列表。 數(shù)據(jù)輸出:分詞后,僅保留人名的金庸武俠小說全集。 樣例輸入: 狄云和戚芳一走到萬家大宅之前,瞧見那高墻朱門、掛燈結(jié)彩的氣派,心中都是暗自嘀咕。戚芳緊緊拉住了父親的衣袖。戚長發(fā)正待向門公詢問,忽見卜垣從門里出來,心中一喜,叫道:“卜賢侄,我來啦。”
    樣例輸出:狄云 戚芳 戚芳 戚長發(fā) 卜垣

1.2 關(guān)鍵問題
1.2.1 中文分詞和人名提取
使用開源的Ansj_seg進(jìn)行分詞。Ansj_seg不僅支持中文分詞,還允許用戶自定義詞典,在分詞前,將人名列表到添加用戶自定義的詞典,可以精確識別金庸武俠小說中的人名。
但實(shí)際測試的時(shí)候發(fā)現(xiàn),Ansj_seg分詞會出現(xiàn)嚴(yán)重的歧義問題,比如“漢子”屬于人名列表中的人名(nr),但Ansj_seg可能會錯(cuò)誤地將它分類為名詞(n)。因此,如果根據(jù)詞性提取人名,會導(dǎo)致最后提取的人名太少。解決方法是在提取人名的時(shí)候,需要在將人名加入用戶自定義詞典的同時(shí),構(gòu)造一個(gè)包含所有人名的字典,對分詞的結(jié)果逐個(gè)進(jìn)行測試,如果在字典里,就是人名。
1.2.2 文件傳輸
使用HDFS傳遞數(shù)據(jù)。考慮到人名列表文件已經(jīng)存放在了HDFS里,所以使用HDFS的方式不需要移動人名列表文件,只需要在Configuration中設(shè)置文件在HDFS文件系統(tǒng)中的路徑,然后在Mapper的setup()函數(shù)里調(diào)用HDFS的函數(shù)獲取文件內(nèi)容即可。
1.2.3 單詞同現(xiàn)算法
兩個(gè)單詞近鄰關(guān)系的定義:實(shí)驗(yàn)要求中已經(jīng)說明,同現(xiàn)關(guān)系為一個(gè)段落。
段落劃分:非常慶幸的是,小說原文中一個(gè)段落就是一行,因此,不需要自己定義FileInputFormat和RecordReader。
1.3 MapReduce設(shè)計(jì)
1.3.1 Mapper

/**
 * 該類做為一個(gè) mapTask 使用。類聲名中所使用的四個(gè)泛型意義為別為:
 *
 * KEYIN:   默認(rèn)情況下,是mr框架所讀到的一行文本的起始偏移量,Long,
 *      但是在hadoop中有自己的更精簡的序列化接口,所以不直接用Long,而用LongWritable
 * VALUEIN: 默認(rèn)情況下,是mr框架所讀到的一行文本的內(nèi)容,String,同上,用Text
 * KEYOUT:  是用戶自定義邏輯處理完成之后輸出數(shù)據(jù)中的key,在此處是單詞,String,同上,用Text
 * VALUEOUT:是用戶自定義邏輯處理完成之后輸出數(shù)據(jù)中的value,在此處是單詞次數(shù),Integer,同上,用IntWritable
 */

public class FirstStepMapper extends Mapper<LongWritable,Text,LongWritable,Text>
{
    Set<String> nameSets = new HashSet<String>();

    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        super.setup(context);

        File file = new File("person/jinyong_all_person.txt");
        FileReader fileReader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String line = bufferedReader.readLine();

        while (line != null)
        {
            //process
            DicLibrary.insert(DicLibrary.DEFAULT, line);
            nameSets.add(line);
            line = bufferedReader.readLine();
        }
    }
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        line = line.replace("道","");

        Result result = DicAnalysis.parse(line);
        List<Term> termList=result.getTerms();
        StringBuilder stringBuilder = new StringBuilder();

        for(Term term:termList){
            if(nameSets.contains(term.getName()))
            {
                stringBuilder.append(term.getName() + '\t');
            }
        }
        context.write(key,new Text(stringBuilder.toString()));
    }
}

1.3.2 Reducer

public class FirstStepReducer extends Reducer<LongWritable,Text,Text,NullWritable>
{
//list()
    @Override
    protected void reduce(LongWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        Text strNameLine = values.iterator().next();
        if(!strNameLine.toString().equals(""))
        {
            context.write(strNameLine,NullWritable.get());
        }

    }
}

1.3.3 Driver

public class FirstStepDriver {

    public static void main(String[] args) throws Exception {
        System.setProperty("HADOOP_USER_NAME", "root") ;
        System.setProperty("hadoop.home.dir", "e:/hadoop-2.8.3");
        if (args == null || args.length == 0) {
            return;
        }

        FileUtil.deleteDir(args[1]);


        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);
        //jar
        job.setJarByClass(FirstStepDriver.class);


        job.setMapperClass(FirstStepMapper.class);
        job.setReducerClass(FirstStepReducer.class);

        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(Text.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileInputFormat.setMaxInputSplitSize(job, 1024*1024);
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean bResult = job.waitForCompletion(true);
        System.out.println("--------------------------------");
        System.exit(bResult ? 0 : 1);

    }
}

2 任務(wù)2 特征抽取:人物同現(xiàn)統(tǒng)計(jì)
2.1 任務(wù)描述
完成基于單詞同現(xiàn)算法的人物同現(xiàn)統(tǒng)計(jì)。在人物同現(xiàn)分析中,如果兩個(gè)人在原文的同一段落中出現(xiàn),則認(rèn)為兩個(gè)人發(fā)生了一次同現(xiàn)關(guān)系。我們需要對人物之間的同現(xiàn)關(guān)系次數(shù)進(jìn)行統(tǒng)計(jì),同現(xiàn)關(guān)系次數(shù)越多,則說明兩人的關(guān)系越密切。

數(shù)據(jù)輸入:任務(wù)1的輸出
數(shù)據(jù)輸出:在金庸的所有武俠小說中,人物之間的同現(xiàn)次數(shù)
樣例輸入:
狄云 戚芳 戚芳 戚長發(fā) 卜垣
戚芳 卜垣 卜垣
樣例輸出:
<狄云,戚芳> 1 <戚長發(fā),狄云 > 1
<狄云,戚長發(fā)> 1 <戚長發(fā),戚芳 > 1
<狄云,卜垣> 1 <戚長發(fā),卜垣 > 1
<戚芳,狄云 > 1 <卜垣,狄云> 1
<戚芳,戚長發(fā) > 1 <卜垣,戚芳> 2
<戚芳,卜垣 > 2 <卜垣,戚長發(fā)> 1

2.2 關(guān)鍵問題
2.2.1 人名冗余
在同一段中,人名可能多次出現(xiàn),任務(wù)一只負(fù)責(zé)提取出所有的人名,沒有剔除多余的人名,任務(wù)必須在輸出同現(xiàn)次數(shù)之前處理冗余人名。我的做法是在Mapper中創(chuàng)建一個(gè)集合,把所有人名放入集合中,集合會自動剔除冗余的人名。
2.2.2 同現(xiàn)次數(shù)統(tǒng)計(jì)
兩個(gè)人物之間應(yīng)該輸出兩個(gè)鍵值對,如“狄云”和“戚芳”,應(yīng)該輸出“<狄云,戚芳> 1”和“<戚芳,狄云> 1”。多個(gè)段落中允許輸出相同的鍵值對,因此,Reducer中需要整合具有相同鍵的輸出,輸出總的同現(xiàn)次數(shù)。
2.3 MapReduce設(shè)計(jì)
2.3.1 Mapper

public class SecondStepMapper extends Mapper<LongWritable,Text,Text,LongWritable> {
    
    @Override   
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        Set<String> set = new HashSet<>();
        String[] names = value.toString().split("\t");
        for (String name : names) {
            set.add(name);
        }
        for (String name1 : set) {
            for (String name2 : set) {
                if (!name1.equals(name2)) {
                    context.write(new Text(name1 + "_" + name2), new LongWritable(1));
                }
            }

        }

    }
}

2.3.2 Reducer

public class SecondStepReduce extends Reducer<Text,LongWritable,Text,LongWritable> {

    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
        int sum=0;
        for ( LongWritable value: values) {
            sum+=value.get();
        }
        context.write(key, new LongWritable(sum));

    }
}

3 任務(wù)3 特征處理:人物關(guān)系圖構(gòu)建與特征歸一化
3.1 任務(wù)描述
根據(jù)任務(wù)2人物之間的共現(xiàn)關(guān)系,生成人物之間的關(guān)系圖。人物關(guān)系使用鄰接表的形式表示,人物是頂點(diǎn),人物之間關(guān)系是邊,兩個(gè)人的關(guān)系的密切程度由共現(xiàn)次數(shù)體現(xiàn),共現(xiàn)次數(shù)越高,邊權(quán)重越高。另外需要對共現(xiàn)次數(shù)進(jìn)行歸一化處理,確保某個(gè)頂點(diǎn)的出邊權(quán)重和為1。

數(shù)據(jù)輸入:任務(wù)2的輸出
數(shù)據(jù)輸出:歸一化權(quán)重后的人物關(guān)系圖
樣例輸入:
<狄云,戚芳> 1 <戚長發(fā),狄云 > 1
<狄云,戚長發(fā)> 1 <戚長發(fā),戚芳 > 1
<狄云,卜垣> 1 <戚長發(fā),卜垣 > 1
<戚芳,狄云 > 1 <卜垣,狄云> 1
<戚芳,戚長發(fā) > 1 <卜垣,戚芳> 2
<戚芳,卜垣 > 2 <卜垣,戚長發(fā)> 1
樣例輸出:
狄云\t戚芳:0.33333;戚長發(fā):0.333333;卜垣:0.333333
戚芳\t狄云:0.25 ;戚長發(fā):0.25;卜垣:0.5
戚長發(fā)\t狄云:0.33333;戚芳:0.333333;卜垣:0.333333
卜垣\t狄云:0.25;戚芳:0.5;戚長發(fā):0.25

3.2 關(guān)鍵問題
3.2.1 確保人物的所有鄰居輸出到相同結(jié)點(diǎn)處理
在Mapper結(jié)點(diǎn)將輸入的鍵值對“<狄云,戚芳> 1”拆分,輸出新的鍵值對“<狄云> 戚芳:1”,“狄云”的所有鄰居會被分配給同一個(gè)Reducer結(jié)點(diǎn)處理。
3.2.2 歸一化
在Reducer結(jié)點(diǎn)首先統(tǒng)計(jì)該人物與所有鄰居同現(xiàn)的次數(shù)和sum,每個(gè)鄰居的的同現(xiàn)次數(shù)除以sum就得到共現(xiàn)概率。為了提高效率,在第一次遍歷鄰居的時(shí)候,可以把名字和共現(xiàn)次數(shù)保存在鏈表里,避免重復(fù)處理字符串。
3.3 MapReduce設(shè)計(jì)
3.3.1 Mapper

package com.neuedu;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;


public class ThirdStepMapper extends Mapper<LongWritable,Text,Text,Text>
{
//    齊元凱_吳應(yīng)熊   1
//    齊元凱_神照上人  1
//    齊元凱_韋小寶   5
//    齊元凱_鰲拜    1
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] arrs = value.toString().split("\t");
        String[] arrs2 = arrs[0].split("_");
        String outValue = arrs2[1] + " " + arrs[1];//吳應(yīng)熊,1
        context.write(new Text(arrs2[0]), new Text(outValue));
    }

}

3.3.2 Reducer

package com.neuedu;

//<I,1><have,1><a,1><dream,1><a,1><dream,1>


import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


public class ThirdStepReducer extends Reducer<Text,Text,Text,NullWritable>
{
//<齊元凱,List(吳應(yīng)熊 1,神照上人 1,韋小寶 5,鰲拜 1)>

//    齊元凱\t吳應(yīng)熊:0.125;神照上人:0.125;韋小寶:0.625;鰲拜:0.125
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        double sum = 0;
       List<String> textList = new ArrayList<String>();
       for(Text text : values)
       {
           String[] split = text.toString().split(" ");
           long count = Long.parseLong(split[1]);
           sum += count;
           textList.add(text.toString());
       }
        //<齊元凱,List(吳應(yīng)熊 1,神照上人 1,韋小寶 5,鰲拜 1)>

//    齊元凱\t吳應(yīng)熊:0.125;神照上人:0.125;韋小寶:0.625;鰲拜:0.125
       StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(key.toString());
        stringBuilder.append("\t");
        stringBuilder.append("0.1#");
        for(String text : textList)
        {
            String[] split = text.split(" ");
            stringBuilder.append(split[0]);
            stringBuilder.append(":");
            double rate = Long.parseLong(split[1])/sum;
            stringBuilder.append(String.valueOf(rate));
            stringBuilder.append(";");
        }
        String output = stringBuilder.toString();
        output = output.substring(0,output.length()-1);
        context.write(new Text(output),NullWritable.get());
    }
}

4 任務(wù)4 數(shù)據(jù)分析:基于人物關(guān)系圖的PageRank計(jì)算

4.1 任務(wù)描述
經(jīng)過數(shù)據(jù)預(yù)處理并獲得任務(wù)的關(guān)系圖之后,就可以對人物關(guān)系圖作數(shù)據(jù)分析,其中一個(gè)典型的分析任務(wù)是:PageRank 值計(jì)算。通過計(jì)算 PageRank,我們就可以定量地獲知金庸武俠江湖中的“主角”們是哪些。
4.2 PageRank原理
PageRank算法由Google的兩位創(chuàng)始人佩奇和布林在研究網(wǎng)頁排序問題時(shí)提出,其核心思想是:如果一個(gè)網(wǎng)頁被很多其它網(wǎng)頁鏈接到,說明這個(gè)網(wǎng)頁很重要,它的PageRank值也會相應(yīng)較高;如果一個(gè)PageRank值很高的網(wǎng)頁鏈接到另外某個(gè)網(wǎng)頁,那么那個(gè)網(wǎng)頁的PageRank值也會相應(yīng)地提高。
相應(yīng)地,PageRank算法應(yīng)用到人物關(guān)系圖上可以這么理解:如果一個(gè)人物與多個(gè)人物存在關(guān)系連接,說明這個(gè)人物是重要的,其PageRank值響應(yīng)也會較高;如果一個(gè)PageRank值很高的人物與另外一個(gè)人物之間有關(guān)系連接,那么那個(gè)人物的PageRank值也會相應(yīng)地提高。一個(gè)人物的PageRank值越高,他就越可能是小說中的主角。
PageRank有兩個(gè)比較常用的模型:簡單模型和隨機(jī)瀏覽模型。由于本次設(shè)計(jì)考慮的是人物關(guān)系而不是網(wǎng)頁跳轉(zhuǎn),因此簡單模型比較合適。簡單模型的計(jì)算公式如下,其中Bi為所有連接到人物i的集合,Lj為認(rèn)為人物j對外連接邊的總數(shù):

在這里插入圖片描述

在本次設(shè)計(jì)的任務(wù)3中,已經(jīng)對每個(gè)人物的邊權(quán)值進(jìn)行歸一化處理,邊的權(quán)值可以看做是對應(yīng)連接的人物占總邊數(shù)的比例。設(shè)表示人物i在人物j所有邊中所占的權(quán)重,則PageRank計(jì)算公式可以改寫為:

在這里插入圖片描述

4.3 PageRank在mapreduce上的實(shí)現(xiàn)細(xì)節(jié)
4.3.1 GraphBuilder類
PageRank需要迭代來實(shí)現(xiàn)PageRank值的更新,因而需要先給每個(gè)人物初始化一個(gè)PageRank值以供迭代過程的執(zhí)行,該部分由GraphBuilder類完成,僅包含一個(gè)Map過程。由于算法收斂與初始值無關(guān),所以我們將每個(gè)人物的PageRank值都初始化為0.1,以任務(wù)3的輸出文件作為Map輸入,可以得到如下樣例格式:

數(shù)據(jù)輸入:任務(wù)3的輸出
數(shù)據(jù)輸出:人物的PageRank值
樣例輸入:
一燈大師 完顏萍:0.005037783;小龍女:0.017632242;……
樣例輸出:
一燈大師 0.1#完顏萍:0.005037783;小龍女:0.017632242;……

4.3.2 PageRanklter類
GraphBuilder將數(shù)據(jù)處理成可供迭代的格式,PageRank的迭代過程由PageRanklter類實(shí)現(xiàn),包含一個(gè)Map和Reduce過程。Map過程產(chǎn)生兩種類型的<key,value>:<人物名,PageRrank值>,<人物名,關(guān)系鏈表>。第一個(gè)人物名是關(guān)系鏈表中的各個(gè)鏈出人物名,其PR值由計(jì)算得到;第二個(gè)人物名是本身人物名,目的是為了保存該人物的鏈出關(guān)系,以保證完成迭代過程。以上面的輸出為例,則Map過程產(chǎn)生的鍵值對為<完顏萍, 1.00.005037>,<小龍女, 1.00.017632>,……,<一燈大師, #完顏萍:0.005037783;……>。
Reduce過程將同一人物名的<key,value>匯聚在一起,如果value是PR值,則累加到sum變量;如果value是關(guān)系鏈表則保存為List。遍歷完迭代器里所有的元素后輸出鍵值對<人物名,sum#List>,這樣就完成了一次迭代過程。
PR值排名不變的比例隨迭代次數(shù)變化的關(guān)系圖如下,由于我們考慮的是找出小說中的主角,所以只要關(guān)心PR值前100名的人物的排名的變化情況,可以看到迭代次數(shù)在10以后,PR值排名不變的比例已經(jīng)趨于穩(wěn)定了,所以基于效率考慮,選取10作為PR的迭代次數(shù)。
4.3.3 PageRankViewer類
當(dāng)所有迭代都完成后,我們就可以對所有人物的PageRank值進(jìn)行排序,該過程由PageRankViewer類完成,包含一個(gè)Map和Reduce過程。Map過程只提取迭代過程輸出結(jié)果中的人物名以及對應(yīng)的PageRank值,并以PageRank值作為key,人物名作為value輸出。為了實(shí)現(xiàn)PageRank值從大到小排序,需要實(shí)現(xiàn)DescFloatComparator類來重寫compare方法以達(dá)成逆序排序。由于可能存在PageRank值相同的情況,所以還需要一個(gè)reduce過程來把因PageRank值相同而匯聚到一起的人物名拆開并輸出。

PageRankMapper

package com.neuedu;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;


public class FourStepMapper extends Mapper<LongWritable,Text,Text,Text>
{
//凌霜華   0.1#狄云:0.3333333333333333;丁典:0.3333333333333333;戚芳:0.3333333333333333
//凌霜華   #狄云:0.3333333333333333;丁典:0.3333333333333333;戚芳:0.3333333333333333
//<凌霜華,List(xxx,yyy,zzz,#狄云:0.3333333333333333;丁典:0.3333333333333333;戚芳:0.3333333333333333)
//凌霜華   1.26#狄云:0.3333333333333333;丁典:0.3333333333333333;戚芳:0.3333333333333333
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] split = value.toString().split("#");
        String[] split2 = split[0].split("\t");
        String mainPeopleName = split2[0];
        String mainPeopleRank = split2[1];
        String[] split1 = split[1].split(";");
        for(String string : split1)
        {
            String[] split3 = string.split(":");
            double relation_value = Double.parseDouble(split3[1]);
            context.write(new Text(split3[0]),new Text(String.valueOf(relation_value * Double.parseDouble(mainPeopleRank))));
        }

        context.write(new Text(mainPeopleName),new Text("#"+split[1]));
    }

}

PageRankReducer

package com.neuedu;
//<I,1><have,1><a,1><dream,1><a,1><dream,1>

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class FourStepReducer extends Reducer<Text,Text,Text,Text>
{
    //<凌霜華,List(xxx,yyy,zzz,#狄云:0.3333333333333333;丁典:0.3333333333333333;戚芳:0.3333333333333333)
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        double sum = 0;
        String tail = "";
//        凌霜華   0.1#狄云:0.3333333333333333;丁典:0.3333333333333333;戚芳:0.3333333333333333
        for(Text text : values)
        {
            if(!text.toString().startsWith("#"))
            {
                sum+=Double.parseDouble(text.toString());
            }
            else
            {
                tail = text.toString();
            }
        }
        context.write(key,new Text(sum + tail));


    }
}

Driver類

package com.neuedu;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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;
public class FourStepDriver {

    public static void main(String[] args) throws Exception {
        System.setProperty("HADOOP_USER_NAME", "root") ;
        System.setProperty("hadoop.home.dir", "e:/hadoop-2.8.3");
        if (args == null || args.length == 0) {
            return;
        }

//        FileUtil.deleteDir(args[1]);

        for(int i = 3; i < 13; i++)
        {
            Configuration configuration = new Configuration();
            Job job = Job.getInstance(configuration);
            //jar
            job.setJarByClass(FourStepDriver.class);


            job.setMapperClass(FourStepMapper.class);
            job.setReducerClass(FourStepReducer.class);

            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(Text.class);

            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(Text.class);

            String inputPath = "output" + i + "/part-r-00000";
            FileInputFormat.setInputPaths(job,new Path(inputPath));
            FileInputFormat.setMaxInputSplitSize(job, 1024*1024);
            String outputPath = "output" + (i + 1);
            FileOutputFormat.setOutputPath(job,new Path(outputPath));

            boolean bResult = job.waitForCompletion(true);
            System.out.println("--------------------------------");
        }


        System.exit(0);

    }
}

5 任務(wù)5 數(shù)據(jù)分析:基于人物關(guān)系圖的標(biāo)簽傳播

5.1 任務(wù)描述
標(biāo)簽傳播(Label Propagation)是一種半監(jiān)督的圖分析算法,他能為圖上的頂點(diǎn)打標(biāo)簽,進(jìn)行圖頂點(diǎn)的聚類分析,從而在一張類似社交網(wǎng)絡(luò)圖中完成社區(qū)發(fā)現(xiàn)。在人物關(guān)系圖中,通過標(biāo)簽傳播算法可以將關(guān)聯(lián)度比較大的人物分到同一標(biāo)簽,可以直觀地分析人物間的關(guān)系。
5.2 標(biāo)簽傳播算法原理
標(biāo)簽傳播算法(Label Propagation Algorithm,后面簡稱LPA)是由Zhu等人于2002年提出,它是一種基于圖的半監(jiān)督學(xué)習(xí)方法,其基本思路是用已標(biāo)記節(jié)點(diǎn)的標(biāo)簽信息去預(yù)測未標(biāo)記節(jié)點(diǎn)的標(biāo)簽信息。LPA基本過程為:(1)每個(gè)結(jié)點(diǎn)初始化一個(gè)特定的標(biāo)簽值;(2)逐輪更新所有節(jié)點(diǎn)的標(biāo)簽,直到所有節(jié)點(diǎn)的標(biāo)簽不再發(fā)生變化為止。對于每一輪刷新,節(jié)點(diǎn)標(biāo)簽的刷新規(guī)則如下:對于某一個(gè)節(jié)點(diǎn),考察其所有鄰居節(jié)點(diǎn)的標(biāo)簽,并進(jìn)行統(tǒng)計(jì),將出現(xiàn)個(gè)數(shù)最多的那個(gè)標(biāo)簽賦值給當(dāng)前節(jié)點(diǎn)。當(dāng)個(gè)數(shù)最多的標(biāo)簽不唯一時(shí),隨機(jī)選擇一個(gè)標(biāo)簽賦值給當(dāng)前節(jié)點(diǎn)。
LPA與PageRank算法相似,同樣需要通過迭代過程來完成。在標(biāo)簽傳播算法中,節(jié)點(diǎn)的標(biāo)簽更新通常有同步更新和異步更新兩種方法。同步更新是指,節(jié)點(diǎn)x在t時(shí)刻的更新是基于鄰接節(jié)點(diǎn)在t-1時(shí)刻的標(biāo)簽。異步更新是指,節(jié)點(diǎn)x在t時(shí)刻更新時(shí),其部分鄰接節(jié)點(diǎn)是t時(shí)刻更新的標(biāo)簽,還有部分的鄰接節(jié)點(diǎn)是t-1時(shí)刻更新的標(biāo)簽。若LPA算法在標(biāo)簽傳播過程中采用的是同步更新,則在二分結(jié)構(gòu)網(wǎng)絡(luò)中,容易出現(xiàn)標(biāo)簽震蕩的現(xiàn)象。在本次設(shè)計(jì)中,我們考慮到了兩種更新方法,并進(jìn)行了比較。
5.3 標(biāo)簽傳播算法在mapreduce上的實(shí)現(xiàn)細(xì)節(jié)
5.3.1 LPAInit類
為實(shí)現(xiàn)LPA的迭代過程,需要先給每個(gè)人物賦予一個(gè)獨(dú)特標(biāo)簽,標(biāo)簽初始化由LPAInit類完成,僅包含一個(gè)Map過程。標(biāo)簽由數(shù)字表示,Map過程由1開始,為每一個(gè)人物名賦予一個(gè)獨(dú)特的標(biāo)簽。為了便于后面的可視化分析,我們需要把PageRank值和標(biāo)簽整合在一起,所以LPAInit的輸入文件直接采用PageRank過程的輸出文件,格式如下:

數(shù)據(jù)輸入:任務(wù)3的輸出
數(shù)據(jù)輸出:附帶初始標(biāo)簽的輸出
樣例輸入:
一燈大師 完顏萍:0.005037783;小龍女:0.017632242;……
樣例輸出:
一燈大師# 0.06955 0.1#完顏萍:0.005037783;小龍女:0.017632242;……

5.3.2 LPAIteration類
LPAIteration類完成標(biāo)簽的更新過程,其格式與LPAInit的輸出格式一致,包含一個(gè)Map和Reduce過程。Map過程對輸入的每一行進(jìn)行切割,輸出四種格式的<key,value>:<人物名,關(guān)系鏈表>,<人物名,PageRank值>,<人物名,標(biāo)簽>,<鏈出人物名,標(biāo)簽#起點(diǎn)人物名>。第四種格式個(gè)鍵值對是為了將該節(jié)點(diǎn)的標(biāo)簽傳給其所有鄰居。
Reduce過程對value值進(jìn)行識別,識別可以通過Map過程把預(yù)先定義好的特殊字符如‘#’、‘@’來實(shí)現(xiàn)前綴到value上來實(shí)現(xiàn)。由于人物關(guān)系圖中的各個(gè)邊都是有權(quán)重的,并且代表兩個(gè)人物的相關(guān)程度,所以標(biāo)簽更新過程不是用邊數(shù)最多的標(biāo)簽而是權(quán)重最大標(biāo)簽來更新,我們可以預(yù)先把權(quán)重最大的若干個(gè)保存到一個(gè)鏈表中,如果存在多個(gè)權(quán)重相同的標(biāo)簽,則隨機(jī)選取一個(gè)作為該人名新的標(biāo)簽。異步方法更新標(biāo)簽需要使用一個(gè)哈希表來存儲已經(jīng)更新標(biāo)簽的人物名和它們的新標(biāo)簽,并且在更新標(biāo)簽時(shí)使用該哈希表里面的標(biāo)簽。同步方法更新標(biāo)簽則不需要存儲已更新的標(biāo)簽。
本次設(shè)計(jì)中比較了同步和異步更新兩種方法,下圖為標(biāo)簽不變的比例隨迭代次數(shù)的變化。可以發(fā)現(xiàn),異步收斂速度更快,只要6次迭代即可完全收斂,且標(biāo)簽不變的比例可達(dá)100%。而同步更新方法則不能達(dá)到100%,說明人物關(guān)系圖中存在子圖是二部子圖。
5.3.3 LPAReorganize類
LPA算法迭代收斂后,所有人物名的標(biāo)簽不再變化,但是此時(shí)的標(biāo)簽排列是散亂的,需要把同一標(biāo)簽的人物名整合在一起。該過程由LPAReorganize類完成,包含一個(gè)Map和Reduce過程。Map過程對輸入的每一行進(jìn)行切割,以<標(biāo)簽,人物名#PageRank值#關(guān)系鏈表>格式輸出。Reduce過程中,同一標(biāo)簽的人物名匯聚在一起,然后根據(jù)每個(gè)標(biāo)簽人物集合的大小從大到小排序,重新賦予標(biāo)簽(從1開始)。這樣輸出文件中同一標(biāo)簽的人物名就會聚集在一起。最后的輸出格式如下:

數(shù)據(jù)輸入:LPAInit的輸出
數(shù)據(jù)輸出:人物的標(biāo)簽信息和PR值
樣例輸入:
一燈大師#0.06955 1#完顏萍:0.005037783;小龍女:0.017632242;……
樣例輸出:
1#一燈大師#0.06955 完顏萍:0.005037783;小龍女:0.017632242;……

5.3.2 LPAMapper類
LPAIteration類完成標(biāo)簽的更新過程,其格式與LPAInit的輸出格式一致,包含一個(gè)Map和Reduce過程。Map過程對輸入的每一行進(jìn)行切割,輸出四種格式的<key,value>:<人物名,關(guān)系鏈表>,<人物名,PageRank值>,<人物名,標(biāo)簽>,<鏈出人物名,標(biāo)簽#起點(diǎn)人物名>。第四種格式個(gè)鍵值對是為了將該節(jié)點(diǎn)的標(biāo)簽傳給其所有鄰居。

package com.neuedu;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class FiveStepMapper extends Mapper<LongWritable,Text,Text,Text>
{
//1#一燈大師    0.16643123198477025#完顏萍:0.004662004662004662;小龍女:0.018648018648018648;尹克西:0.002331002331002331;尼摩星:0.006993006993006993;張無忌:0.004662004662004662;無色:0.002331002331002331;明月:0.002331002331002331;朱九真:0.004662004662004662;朱子柳:0.027972027972027972;朱長齡:0.002331002331002331;李莫愁:0.009324009324009324;楊康:0.006993006993006993;楊過:0.07692307692307693;柯鎮(zhèn)惡:0.004662004662004662;梅超風(fēng):0.002331002331002331;樵子:0.02097902097902098;歐陽克:0.006993006993006993;武三娘:0.004662004662004662;武三通:0.030303030303030304;武修文:0.006993006993006993;武敦儒:0.004662004662004662;洪七公:0.023310023310023312;漁人:0.02564102564102564;瀟湘子:0.002331002331002331;點(diǎn)蒼漁隱:0.006993006993006993;王處一:0.004662004662004662;琴兒:0.002331002331002331;穆念慈:0.002331002331002331;老頭子:0.002331002331002331;耶律燕:0.006993006993006993;耶律齊:0.011655011655011656;裘千丈:0.002331002331002331;裘千仞:0.030303030303030304;上官:0.002331002331002331;裘千尺:0.013986013986013986;覺遠(yuǎn):0.002331002331002331;覺遠(yuǎn)大師:0.002331002331002331;說不得:0.002331002331002331;達(dá)爾巴:0.004662004662004662;郝大通:0.004662004662004662;郭芙:0.009324009324009324;郭襄:0.039627039627039624;郭靖:0.11655011655011654;金輪法王:0.009324009324009324;陸乘風(fēng):0.002331002331002331;陸無雙:0.016317016317016316;霍都:0.006993006993006993;馬鈺:0.004662004662004662;魯有腳:0.009324009324009324;黃藥師:0.03263403263403263;黃蓉:0.16783216783216784;小沙彌:0.006993006993006993;丘處機(jī):0.011655011655011656;喬寨主:0.002331002331002331;書生:0.03496503496503497;農(nóng)夫:0.037296037296037296;華箏:0.002331002331002331;衛(wèi)璧:0.004662004662004662;呂文德:0.002331002331002331;周伯通:0.06060606060606061;啞巴:0.002331002331002331;啞梢公:0.002331002331002331;大漢:0.002331002331002331;天竺僧:0.002331002331002331;天竺僧人:0.006993006993006993
//<完顏萍,1#一燈大師>
//<小龍女,1#一燈大師>
//        ...
//<天竺僧人,1#一燈大師>

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] split = value.toString().split("\t");
        String[] split1 = split[0].split("#");
        String mainPeople = split1[1];
        String[] split2 = split[1].split("#");

        //<一燈大師,#完顏萍:0.004662004662004662;小龍女:0.018648018648018648;尹克西:0.002331002331002331;尼摩星:0.006993006993006993;張無忌:0.004662004662004662;無色:0.002331002331002331;明月:0.002331002331002331;朱九真:0.004662004662004662;朱子柳:0.027972027972027972;朱長齡:0.002331002331002331;李莫愁:0.009324009324009324;楊康:0.006993006993006993;楊過:0.07692307692307693;柯鎮(zhèn)惡:0.004662004662004662;梅超風(fēng):0.002331002331002331;樵子:0.02097902097902098;歐陽克:0.006993006993006993;武三娘:0.004662004662004662;武三通:0.030303030303030304;武修文:0.006993006993006993;武敦儒:0.004662004662004662;洪七公:0.023310023310023312;漁人:0.02564102564102564;瀟湘子:0.002331002331002331;點(diǎn)蒼漁隱:0.006993006993006993;王處一:0.004662004662004662;琴兒:0.002331002331002331;穆念慈:0.002331002331002331;老頭子:0.002331002331002331;耶律燕:0.006993006993006993;耶律齊:0.011655011655011656;裘千丈:0.002331002331002331;裘千仞:0.030303030303030304;上官:0.002331002331002331;裘千尺:0.013986013986013986;覺遠(yuǎn):0.002331002331002331;覺遠(yuǎn)大師:0.002331002331002331;說不得:0.002331002331002331;達(dá)爾巴:0.004662004662004662;郝大通:0.004662004662004662;郭芙:0.009324009324009324;郭襄:0.039627039627039624;郭靖:0.11655011655011654;金輪法王:0.009324009324009324;陸乘風(fēng):0.002331002331002331;陸無雙:0.016317016317016316;霍都:0.006993006993006993;馬鈺:0.004662004662004662;魯有腳:0.009324009324009324;黃藥師:0.03263403263403263;黃蓉:0.16783216783216784;小沙彌:0.006993006993006993;丘處機(jī):0.011655011655011656;喬寨主:0.002331002331002331;書生:0.03496503496503497;農(nóng)夫:0.037296037296037296;華箏:0.002331002331002331;衛(wèi)璧:0.004662004662004662;呂文德:0.002331002331002331;周伯通:0.06060606060606061;啞巴:0.002331002331002331;啞梢公:0.002331002331002331;大漢:0.002331002331002331;天竺僧:0.002331002331002331;天竺僧人:0.006993006993006993>
        context.write(new Text(mainPeople),new Text("#" + split2[1]));
        context.write(new Text(mainPeople),new Text("@" + split2[0]));

        String[] split3 = split2[1].split(";");
        //<完顏萍,1#一燈大師>
//<小龍女,1#一燈大師>
//        ...
//<天竺僧人,1#一燈大師>
        for(String string : split3)
        {
            String[] split4 = string.split(":");
            context.write(new Text(split4[0]),new Text(split[0]));
        }
    }
}

5.3.2 LPAReducer類
Reduce過程對value值進(jìn)行識別,識別可以通過Map過程把預(yù)先定義好的特殊字符如‘#’、‘@’來實(shí)現(xiàn)前綴到value上來實(shí)現(xiàn)。由于人物關(guān)系圖中的各個(gè)邊都是有權(quán)重的,并且代表兩個(gè)人物的相關(guān)程度,所以標(biāo)簽更新過程不是用邊數(shù)最多的標(biāo)簽而是權(quán)重最大標(biāo)簽來更新,我們可以預(yù)先把權(quán)重最大的若干個(gè)保存到一個(gè)鏈表中,如果存在多個(gè)權(quán)重相同的標(biāo)簽,則隨機(jī)選取一個(gè)作為該人名新的標(biāo)簽。異步方法更新標(biāo)簽需要使用一個(gè)哈希表來存儲已經(jīng)更新標(biāo)簽的人物名和它們的新標(biāo)簽,并且在更新標(biāo)簽時(shí)使用該哈希表里面的標(biāo)簽。同步方法更新標(biāo)簽則不需要存儲已更新的標(biāo)簽。

package com.neuedu;

//<I,1><have,1><a,1><dream,1><a,1><dream,1>


import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class FiveStepReducer extends Reducer<Text,Text,Text,Text>
{
    Map<String,String> mapGlobalName2Label = new HashMap<>();
    //<一燈大師,#完顏萍:0.004662004662004662;小龍女:0.018648018648018648;尹克西:0.002331002331002331;尼摩星:0.006993006993006993;張無忌:0.004662004662004662;無色:0.002331002331002331;明月:0.002331002331002331;朱九真:0.004662004662004662;朱子柳:0.027972027972027972;朱長齡:0.002331002331002331;李莫愁:0.009324009324009324;楊康:0.006993006993006993;楊過:0.07692307692307693;柯鎮(zhèn)惡:0.004662004662004662;梅超風(fēng):0.002331002331002331;樵子:0.02097902097902098;歐陽克:0.006993006993006993;武三娘:0.004662004662004662;武三通:0.030303030303030304;武修文:0.006993006993006993;武敦儒:0.004662004662004662;洪七公:0.023310023310023312;漁人:0.02564102564102564;瀟湘子:0.002331002331002331;點(diǎn)蒼漁隱:0.006993006993006993;王處一:0.004662004662004662;琴兒:0.002331002331002331;穆念慈:0.002331002331002331;老頭子:0.002331002331002331;耶律燕:0.006993006993006993;耶律齊:0.011655011655011656;裘千丈:0.002331002331002331;裘千仞:0.030303030303030304;上官:0.002331002331002331;裘千尺:0.013986013986013986;覺遠(yuǎn):0.002331002331002331;覺遠(yuǎn)大師:0.002331002331002331;說不得:0.002331002331002331;達(dá)爾巴:0.004662004662004662;郝大通:0.004662004662004662;郭芙:0.009324009324009324;郭襄:0.039627039627039624;郭靖:0.11655011655011654;金輪法王:0.009324009324009324;陸乘風(fēng):0.002331002331002331;陸無雙:0.016317016317016316;霍都:0.006993006993006993;馬鈺:0.004662004662004662;魯有腳:0.009324009324009324;黃藥師:0.03263403263403263;黃蓉:0.16783216783216784;小沙彌:0.006993006993006993;丘處機(jī):0.011655011655011656;喬寨主:0.002331002331002331;書生:0.03496503496503497;農(nóng)夫:0.037296037296037296;華箏:0.002331002331002331;衛(wèi)璧:0.004662004662004662;呂文德:0.002331002331002331;周伯通:0.06060606060606061;啞巴:0.002331002331002331;啞梢公:0.002331002331002331;大漢:0.002331002331002331;天竺僧:0.002331002331002331;天竺僧人:0.006993006993006993>
//<一燈大師,100#尹克西>
//<一燈大師,250#完顏萍>
//    ...
//<一燈大師,List[100#尹克西,250#完顏萍,#完顏萍:0.004662004662004662;小龍女:0.018648018648018648;尹克西:0.002331002331002331;尼摩星:0.006993006993006993;張無忌:0.004662004662004662;無色:0.002331002331002331;明月:0.002331002331002331;朱九真:0.004662004662004662;朱子柳:0.027972027972027972;朱長齡:0.002331002331002331;李莫愁:0.009324009324009324;楊康:0.006993006993006993;楊過:0.07692307692307693;柯鎮(zhèn)惡:0.004662004662004662;梅超風(fēng):0.002331002331002331;樵子:0.02097902097902098;歐陽克:0.006993006993006993;武三娘:0.004662004662004662;武三通:0.030303030303030304;武修文:0.006993006993006993;武敦儒:0.004662004662004662;洪七公:0.023310023310023312;漁人:0.02564102564102564;瀟湘子:0.002331002331002331;點(diǎn)蒼漁隱:0.006993006993006993;王處一:0.004662004662004662;琴兒:0.002331002331002331;穆念慈:0.002331002331002331;老頭子:0.002331002331002331;耶律燕:0.006993006993006993;耶律齊:0.011655011655011656;裘千丈:0.002331002331002331;裘千仞:0.030303030303030304;上官:0.002331002331002331;裘千尺:0.013986013986013986;覺遠(yuǎn):0.002331002331002331;覺遠(yuǎn)大師:0.002331002331002331;說不得:0.002331002331002331;達(dá)爾巴:0.004662004662004662;郝大通:0.004662004662004662;郭芙:0.009324009324009324;郭襄:0.039627039627039624;郭靖:0.11655011655011654;金輪法王:0.009324009324009324;陸乘風(fēng):0.002331002331002331;陸無雙:0.016317016317016316;霍都:0.006993006993006993;馬鈺:0.004662004662004662;魯有腳:0.009324009324009324;黃藥師:0.03263403263403263;黃蓉:0.16783216783216784;小沙彌:0.006993006993006993;丘處機(jī):0.011655011655011656;喬寨主:0.002331002331002331;書生:0.03496503496503497;農(nóng)夫:0.037296037296037296;華箏:0.002331002331002331;衛(wèi)璧:0.004662004662004662;呂文德:0.002331002331002331;周伯通:0.06060606060606061;啞巴:0.002331002331002331;啞梢公:0.002331002331002331;大漢:0.002331002331002331;天竺僧:0.002331002331002331;天竺僧人:0.006993006993006993>]
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {

        Map<String,Double> mapName2Weight = new HashMap<>();
        Map<String,String> mapName2Label = new HashMap<>();


        String peopleRelation = "";
        String peoplePR = "";
        List<String> labelPeopleNameList = new ArrayList<>();
        for(Text text : values)
        {
            String content = text.toString();
            if(content.startsWith("#"))
            {
                peopleRelation = content.replace("#","");
            }
            else if(content.startsWith("@"))
            {
                peoplePR = content.replace("@","");
            }
            else
            {
                //List[100#尹克西,250#完顏萍]
                labelPeopleNameList.add(content);
            }
        }

        String[] arrNameWeight = peopleRelation.split(";");
        for(String string : arrNameWeight)
        {
            //小龍女:0.018648018648018648
            String[] split = string.split(":");
            mapName2Weight.put(split[0],Double.parseDouble(split[1]));
        }

        Map<String,Double> mapLabel2Weight =  new HashMap<>();
        for(String string : labelPeopleNameList)
        {
            //100#尹克西
            String[] split = string.split("#");
            mapName2Label.put(split[1],split[0]);

//            7  zhangsan 0.3
//            7  wangwu   0.4
            String label = split[0];
            if(mapGlobalName2Label.containsKey(split[1]))
            {
                label = mapGlobalName2Label.get(split[1]);
            }

            if(!mapLabel2Weight.containsKey(label))
            {
                mapLabel2Weight.put(label,mapName2Weight.get(split[1]));
            }
            else
            {
                Double weight = mapLabel2Weight.get(label);
                weight += mapName2Weight.get(split[1]);
                mapLabel2Weight.put(label,weight);
            }



        }

        //比較主要人物周圍的人,找到關(guān)系最密切的那個(gè),將主要人物的標(biāo)簽label更新成那個(gè)人的label
        double maxWeight = 0;
//        String peopleMax = "";
        String newLabel = "";
        for(Map.Entry<String,Double> entry : mapLabel2Weight.entrySet())
        {
            double weight = entry.getValue();
            if(maxWeight < weight)
            {
                maxWeight = weight;
                newLabel = entry.getKey();
            }
        }

//        String newLabel = mapName2Label.get(peopleMax);
        mapGlobalName2Label.put(key.toString(),newLabel);

        Text keyWrite = new Text(newLabel + "#" + key.toString());
        Text valueWrite = new Text(peoplePR + "#" + peopleRelation);
        context.write(keyWrite,valueWrite);
    }
}

Driver類

package com.neuedu;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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;


public class FiveStepDriver {

    public static void main(String[] args) throws Exception {
        System.setProperty("HADOOP_USER_NAME", "root") ;
        System.setProperty("hadoop.home.dir", "e:/hadoop-2.8.3");
//        if (args == null || args.length == 0) {
//            return;
//        }

//        FileUtil.deleteDir(args[1]);

        for(int i = 14; i < 24; i++)
        {
            Configuration configuration = new Configuration();
            Job job = Job.getInstance(configuration);
            //jar
            job.setJarByClass(FiveStepDriver.class);


            job.setMapperClass(FiveStepMapper.class);
            job.setReducerClass(FiveStepReducer.class);

            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(Text.class);

            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(Text.class);

            String inputPath = "output" + i + "/part-r-00000";
            FileInputFormat.setInputPaths(job,new Path(inputPath));
            FileInputFormat.setMaxInputSplitSize(job, 1024*1024);
            String outputPath = "output" + (i + 1);
            FileOutputFormat.setOutputPath(job,new Path(outputPath));

            boolean bResult = job.waitForCompletion(true);
            System.out.println("--------------------------------");
        }


        System.exit(0);

    }
}

6 數(shù)據(jù)可視化

6.1 可視化工具Gephi
Gephi是一款開源的跨平臺的基于JVM的復(fù)雜網(wǎng)絡(luò)分析軟件。把PageRank和LPA的結(jié)果,轉(zhuǎn)化為gexf格式,在Gephi中繪制圖像并分析大數(shù)據(jù)實(shí)驗(yàn)結(jié)果,更加直觀、易于理解。
gexf實(shí)際上是一種特殊的XML文件,python的gexf庫提供了接口方便我們編輯和生成gexf文件,因此我們選擇使用python處理PageRank和LPA的結(jié)果。頂點(diǎn)有兩種屬性,LPA生成的標(biāo)簽和PageRank計(jì)算的PR值,每條邊的權(quán)重是PageRank計(jì)算出的值。在可視化的時(shí)候,標(biāo)簽決定頂點(diǎn)顯示的顏色,PR值決定標(biāo)簽的
6.2 可視化預(yù)處理
編寫一個(gè)python程序transform2xml.py,將數(shù)據(jù)分析部分得到的PR值,標(biāo)簽以及點(diǎn)連接關(guān)系處理成一個(gè)可供Gephi讀取的gexf文件。
6.3 可視化結(jié)果

在這里插入圖片描述

在這里插入圖片描述

7 輸出結(jié)果截圖


在這里插入圖片描述

7.2 同現(xiàn)次數(shù)統(tǒng)計(jì)


在這里插入圖片描述

7.3 歸一化輸出
在這里插入圖片描述

7.4 PageRank

在這里插入圖片描述

7.5 LPA輸出結(jié)果
在這里插入圖片描述

生成gexf文件

package com.neuedu;


import it.uniroma1.dis.wsngroup.gexf4j.core.*;
import it.uniroma1.dis.wsngroup.gexf4j.core.data.Attribute;
import it.uniroma1.dis.wsngroup.gexf4j.core.data.AttributeClass;
import it.uniroma1.dis.wsngroup.gexf4j.core.data.AttributeList;
import it.uniroma1.dis.wsngroup.gexf4j.core.data.AttributeType;
import it.uniroma1.dis.wsngroup.gexf4j.core.impl.GexfImpl;
import it.uniroma1.dis.wsngroup.gexf4j.core.impl.StaxGraphWriter;
import it.uniroma1.dis.wsngroup.gexf4j.core.impl.data.AttributeListImpl;

import java.io.*;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;


public class App5 {

    public static void main(String[] args) throws IOException {
        Gexf gexf = new GexfImpl();
        Calendar date = Calendar.getInstance();

        gexf.getMetadata()
                .setLastModified(date.getTime())
                .setCreator("Gephi.org")
                .setDescription("A Web network");
        gexf.setVisualization(true);

        Graph graph = gexf.getGraph();
        graph.setDefaultEdgeType(EdgeType.UNDIRECTED).setMode(Mode.STATIC);

        AttributeList attrList = new AttributeListImpl(AttributeClass.NODE);
        graph.getAttributeLists().add(attrList);

        Attribute attUrl = attrList.createAttribute("class", AttributeType.INTEGER, "Class");
        Attribute attIndegree = attrList.createAttribute("pageranks", AttributeType.DOUBLE, "PageRank");

        File file = new File("output25/part-r-00000");
        FileReader fileReader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(fileReader);

        String line = bufferedReader.readLine();
        Map<String,Node> name2Node = new HashMap<>();
        //農(nóng)夫    0.0020768966450448022#丁典:0.5;狄云:0.5
//        Map<農(nóng)夫_丁典,0.5>
        Map<String,Double> namePair2Weight = new HashMap<>();

        Integer nodeId = 0;
        while(line != null)
        {
            String[] split = line.split("\t");
            String[] split1 = split[1].split("#");
            //農(nóng)夫    0.0020768966450448022#丁典:0.5;狄云:0.5
            String[] split2 = split1[1].split(";");

            //1#zhangsan
            String[] arr = split[0].split("#");

            for(String string : split2)
            {
                //丁典:0.5
                String[] split3 = string.split(":");
                namePair2Weight.put(arr[1] + "_" + split3[0], Double.parseDouble(split3[1]));
            }

            Node node = graph.createNode(nodeId.toString());
            nodeId++;
            node.setLabel(arr[1])
                    .getAttributeValues()
                    .addValue(attUrl, arr[0])
                    .addValue(attIndegree, split1[0]);

            name2Node.put(arr[1],node);

            line = bufferedReader.readLine();
        }

        int edgeID = 0;
        for(Map.Entry<String,Double> entry : namePair2Weight.entrySet())
        {
            //農(nóng)夫_丁典
            String namePair = entry.getKey();
            String[] split = namePair.split("_");

            Node node1 = name2Node.get(split[0]);
            Node node2 = name2Node.get(split[1]);
            node1.connectTo(String.valueOf(edgeID++),node2).setWeight(Float.parseFloat(entry.getValue().toString()));
        }
        StaxGraphWriter graphWriter = new StaxGraphWriter();
        File f = new File("static_graph_sample.gexf");
        Writer out;
        try {
            out =  new FileWriter(f, false);
            graphWriter.writeToStream(gexf, out, "UTF-8");
            System.out.println(f.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 項(xiàng)目設(shè)計(jì)目的 通過一個(gè)綜合數(shù)據(jù)分析案例:”金庸的江湖——金庸武俠小說中的人物關(guān)系挖掘“,來學(xué)習(xí)和掌握MapRedu...
    piziyang12138閱讀 3,720評論 0 3
  • 鵬:你在天堂過得好嗎? 你我的緣分怎么那么短暫,才剛過中年你就離我而去… 雖然你沒給我大富大貴的...
    余生的精致閱讀 193評論 2 4
  • “我遇見誰,會有怎樣的對白。我等的人,他在多遠(yuǎn)的未來。”前一陣子,這首“遇見”很是流行,引起了很多人的共鳴。人往往...
    安堇呈閱讀 520評論 0 1
  • 某某先生: 你好,我為什么會想寫信給你呢?那是因?yàn)槲倚枰崭黄恼拢瑢?shí)在找不到題材寫,就寫信給你吧...
    夏目的貓先生閱讀 170評論 0 0