項(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.全本的金庸武俠小說文集(未分詞);
- 金庸武俠小說人名列表。 數(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();
}
}
}