大数据下的关联规则挖掘
DBLP( Digital Bibliography and Library Project )是一个计算机类英文文献的集成数据库系统。DBLP所收录的论文质量较高, 文献更新速度很快, 很好地反应了国际学术研究的前沿方向。DBLP数据可以为人们提供大量有用的知识, 通过对DBLP数据的分析, 可以找到权威作者。对权威作家的分析, 可以挖掘出计算机研究的新领域。
作者合著关系属于社会网络范畴, 目的是研究合著论文的作者间的关系。通过对这种关系的研究,可以使人们了解科学协作的结构, 即协作以何种方式组织的; 了解研究者在科学研究中所处的位置; 还可以应用到多种会议上, 使人们了解在某一特定会议上发表论文的作者间的关系。
通过对DBLP数据的处理,找到DBLP上作者间协作的关系,输出合作次数较多的作者合著关联规则。具体按以下三个方面展开:
1.DBLP数据预处理
从DBLP网站:http://dblp.uni-trier.de/xml/得到DBLP XML记录。研究分析XML文件的内容和特点,选择适合的解析方法得到作者数据集。
同时,可根据实验要求与数据特点对数据集进行适当的筛选,以达到进一步压缩数据的目的。
2.实现格式转化
将解析得到的数据集(一般为csv格式文件)通过weka或者其他编辑器加入头部定义,转化为weka专用的文件格式arff。
3.挖掘关联规则
将arff文件导入到weka中,选用关联规则并设定好各参数的值挖掘频繁项并按作者合著关系的次数由大到小输出关联规则。
原理:
1.关联规则
关联规则概念首先由R. AgrawaI 等于1993 年提出。所谓关联规则,是指客体之间的相互关系。关联规则形如:A1∩A2∩Ai->B1∩B2∩⋯∩Bj,(4%,70%)意味着目标数据中客体B1, B2, ⋯, Bj 倾向于同客体A1,A2,···,Ai
一起出现,其中4%为关联规则的支持度,70%为关联规则的信任度。
2.Apriori算法
Apriori 算法采用的是迭代方法,需要多遍扫描事务数据库, 为了提高频繁项目集的产生效率,可利用一个重要的性质来减少项目搜索空间。Apriori 性质就是规定一个频繁项目集的所有非空子集必需也是频繁项目集。根据这一性质,进行第 遍扫描之前,可先产生候选集C , C 可以分两步来产生,设前一步(第 k-1)步已生成(k-1)-频繁集Lk-1,则首先可以通过对Lk -1中的成员进行联接来产生候选,Lk-1 中的两个成员必需满足在两个成员项目中有k-2个项目是相同的这个条件方可联接,即:C = Lk-1ΘLk-1 = { AΘB|A, BcLk-1, |A∩B|=k-2 }
接着,再从C 中删除所有包含不是频繁的( k-1)-子集的成员项目集即可。
3.矩阵交集算法
关联规则挖掘的难点主要在挖掘频繁项目集,由于其面对的是海量数据,其理论上的搜索空间是2i,该算法存在“项集生成瓶颈”问题,目前,Apriori关联规则优化算法也是针对这个问题提出的.
由于关联规则算法存在上述问题,因此,我们在实验过程中参考一篇学术论文采用了新的关联规则挖掘算法—矩阵交集算法.算法步骤如下:
(1) 产生数据库D':遍历数据库D,筛选出所有大于min_sup的项目,并产生数据库D'(在本实验中表现为在原数据集中将出现次数小于min_up的作者删掉),同时列出在数据库D'中候选项的集合记做ak (2<k<m),所有的频繁1-项集都包含在ak中.
(2) 转换数据形式生成矩阵:由ak(D'中候选项的集合)产生k个列,按TID(人物标识)包含的项目由多到少的规律排列,由行计数器sr 统计每个交易行中项目集合数,由列计数器cr 为列中相同的项目计数,为每个ak 集合中的项
目赋值(0,1),有则赋1,无为0.扫描数据库为每个ak 计数,生成新数据库包含T(t1 ~tn ),并按照交易项数ak 的计数从大到小排列.
(3) 产生交集:由sr 和cr 产生最大项目集和次项目集n个,求其交集t1∩t3,t1∩t4,⋯,t1∩tn 找出最大频繁项集.其可信度由sr 最大项集中的项同时满足cr 较大值来验证.算法应用过程举例如下:
① 取min_up=50%=2,
数据库D
TID |
Items |
100 |
a,c,d,f,n |
200 |
b,c,d,e,f,i,k |
300 |
d,e,f,g,m |
400 |
b,f,p,s |
500 |
c,d,f,s |
600 |
a,b,c,e,h,o |
数据库D’
TID |
Items |
100 |
a,c,d,f |
200 |
b,c,d,e,f |
300 |
d,e,f,g,m |
400 |
b,f,p,s |
500 |
c,d,f,s |
600 |
a,b,c,e,h,o |
② 生成矩阵
TID |
a |
b |
c |
d |
e |
f |
sr |
100 |
1 |
|
1 |
1 |
|
1 |
|
200 |
|
1 |
1 |
1 |
1 |
1 |
|
300 |
|
|
|
1 |
1 |
1 |
|
400 |
|
1 |
|
|
|
1 |
|
500 |
|
|
1 |
1 |
|
1 |
|
600 |
1 |
1 |
1 |
|
1 |
|
|
cr |
|
|
|
|
|
|
|
③计数筛选
TID |
a |
b |
c |
d |
e |
f |
Sr |
200 |
1 |
|
1 |
1 |
|
1 |
5 |
100 |
|
1 |
1 |
1 |
1 |
1 |
4 |
600 |
|
|
|
1 |
1 |
1 |
4 |
300 |
|
1 |
|
|
|
1 |
3 |
500 |
|
|
1 |
1 |
|
1 |
3 |
400 |
1 |
1 |
1 |
|
1 |
|
2 |
cr |
2 |
3 |
4 |
4 |
3 |
5 |
|
产生项集的最大集合和次集合T1、T2 、T3,求其互相的交集:
T1∩T2={b,c,d,e,f}∩{a,c,d,f} ={c,d,f},
T1∩T3={b,c,d,e,f}∩{a,b,c,e} ={b,c,e},
根据cr值和关联规则的性质删除{b,c,e}从而产生最大频繁项集{f,c,d}.
实现:
4.1 过程详解
1. 从DBLP官网(http://dblp.uni-trier.de/xml/)下载得到原始数据文件dblp.xml。使用vi打开:
2. 分析xml文件的标签,得知作者名在<author>和</author>之间,我们现在只提取article的作者名,主要C/C++代码如下:
1 int dblp2csv() 2 { 3 ofstream outfile("dblp.csv") ; 4 ifstream infile("dblp.xml") ; 5 6 string line ; 7 8 for(; getline(infile,line) ;) 9 { 10 if(line.substr(0,8) == "<article") 11 { 12 for(int count = 0 ; getline(infile,line);) 13 { 14 if(line.substr(0,10) == "</article>") 15 { 16 outfile << '\n' ; 17 break ; 18 } 19 if(line.substr(0,8) == "<author>") 20 { 21 if(count != 0) 22 outfile << ',' ; 23 for(string::size_type pos = 8 ; pos != line.size() ; ++pos ) 24 { 25 if(line[pos] == '<') 26 { 27 if(line.substr(pos,9) == "</author>") 28 { 29 count += 1 ; 30 break ; 31 } 32 } 33 else 34 outfile << line[pos] ; 35 } 36 } 37 } 38 } 39 } 40 41 outfile.close(); 42 infile.close(); 43 return 0 ; 44 }
3. 提取作者名的文件dblp.csv:
4. 试图将dblp.csv文件导入到weka时,发现格式不对。
分析:少了第一行属性值,以及每一行属性对应的值如果缺失则需要用缺失值(在arff格式中用‘?’表示)表示,所以在csv文件中每一行没有达到最大属性值个数的需要用逗号隔开,以表示这是缺失值,并非不存在属性值。
因此将dblp.csv文件稍作修改,主要C/C++代码如下:
1 int dblp_mod() 2 { 3 ofstream outfile("dblp_new.csv") ; 4 ifstream infile("dblp.csv") ; 5 6 int max_au = 0 ; 7 string line ; 8 9 for(;getline(infile , line);) 10 { 11 if(line == "") 12 continue ; 13 int count = 1 ; 14 for(string::size_type i = 0 ; i != line.size() ; ++i) 15 { 16 if(line[i] == ',') 17 count += 1 ; 18 } 19 if(max_au < count) 20 max_au = count ; 21 } 22 int i = 0 ; 23 for( ; i != max_au ; ++i) 24 { 25 outfile << "auth" << i << ',' ; 26 } 27 outfile << "auth" << i << '\n' ; 28 29 infile.clear(); 30 infile.seekg(0); 31 for( ; getline(infile , line) ; ) 32 { 33 if(line == "") 34 continue ; 35 for(string::size_type n = 0 ; n != (line.size()-1) ; ++n) 36 { 37 outfile << line[n] ; 38 } 39 int count = 0 ; 40 for(string::size_type j = 0 ; j != line.size() ; ++j) 41 { 42 if(line[j] == ',') 43 count += 1 ; 44 } 45 for(int duf = 0 ; duf != (max_au - count - 1) ; ++duf) 46 { 47 outfile << ',' ; 48 } 49 outfile << '\n' ; 50 } 51 infile.close(); 52 outfile.close(); 53 return 0 ; 54 }
5. 修改后的文件dblp_new.csv:
分析:weka中对某些特殊字符不支持,比如说单引号,双引号等,需要把前一步得到的文件中的特殊符号替换成不会出错的符号,比如*。
6. 目前产生的文件有160M左右,weka读这么大小的文件需要2G以上的内存,硬件成了主要瓶颈。
分析:硬件没办法改变,需要找到其他的解决办法,办法之一是将数据压缩。由于本实验的目的是找出作者之间的强关联规则,对具体作者的名字的值没有要求,而目前文件中作者名字基本都是比较长的字符串,所以,字典压缩是我们的首选,并且为了尽可能的减少字符的字数,我们采用62进制(0,1,...9,a,b,...,z,A,B,...,Z),主要C/C++代码如下:
1 int generate_index() 2 { 3 ifstream infile("dblp_new_1019.csv"); 4 ofstream outfile1("dblp_index.csv"); 5 ofstream outfile2("dblp_new_index.csv"); 6 7 string line , temp ; 8 string *index = new string[10000000]; 9 int count = 0 , 10 i = 0 , 11 n = 0 ; 12 13 getline(infile,line); 14 outfile2 << line << '\n'; 15 for(; getline(infile,line) ;) 16 { 17 if(line == "") 18 continue ; 19 for(string::size_type j = 0 ; j != line.size() ; ++j) 20 { 21 if(line[j] != ',') 22 { 23 temp += line[j] ; 24 if(j != (line.size()-1)) 25 continue ; 26 else 27 { 28 for(i = count-1 ; i >= 0 ; --i) 29 { 30 if(temp == index[i]) 31 { 32 outfile2 << ten_to_thrtw(i) << '\n' ; 33 break ; 34 } 35 } 36 if(i < 0) 37 { 38 index[count] = temp ; 39 outfile1 << ten_to_thrtw(count) << ',' << index[count] << '\n'; 40 outfile2 << ten_to_thrtw(count) << '\n' ; 41 ++count; 42 } 43 n = 0 ; 44 temp = ""; 45 } 46 } 47 else if(line[j] == ',') 48 { 49 if(temp == "") 50 { 51 outfile2 << line.substr(j,line.size()-j) << '\n' ; 52 break ; 53 } 54 else 55 { 56 for(i = count-1 ; i >= 0 ; --i) 57 { 58 if(temp == index[i]) 59 { 60 if(j == (line.size()-1)) 61 { 62 outfile2 << ten_to_thrtw(i) << line[j] << '\n' ; 63 } 64 else 65 { 66 outfile2 << ten_to_thrtw(i) << line[j] ; 67 } 68 break ; 69 } 70 } 71 if(i < 0) 72 { 73 index[count] = temp ; 74 outfile1 << ten_to_thrtw(count) << ',' << index[count] << '\n'; 75 if(j == (line.size()-1)) 76 { 77 outfile2 << ten_to_thrtw(count) << line[j] << '\n' ; 78 } 79 else 80 { 81 outfile2 << ten_to_thrtw(count) << line[j] ; 82 } 83 ++count ; 84 } 85 n = 0 ; 86 temp = ""; 87 } 88 } 89 } 90 } 91 92 delete[] index; 93 infile.close(); 94 outfile1.close(); 95 outfile2.close(); 96 return 0 ; 97 } 98 99 100 string ten_to_thrtw(int x) 101 { 102 int m , n ; 103 string obj ; 104 105 for(n = x+1 ; n ; n /= 62) 106 { 107 m = n%62 ; 108 if(m >= 10) 109 { 110 if(m >= 36) 111 obj.push_back('A' + (m-36)); 112 else 113 obj.push_back('a'+(m-10)); 114 } 115 else 116 obj.push_back('0' + m) ; 117 } 118 reverse(obj.begin(),obj.end()); 119 return obj ; 120 }
6. 压缩后的文件dblp_new_index.csv:
8.下图是字典文件dblp_index.csv:
9. 从结果来看,压缩比的方案不可行,实际的压缩比令我们非常失望。
①分析:dblp_new.csv文件中逗号占了绝大多数,并且由于作者人数过大,大多数作者名也需要用三位乃至四位字符表示,而主要原因还是缺失值太多,而这些由于缺失值产生的逗号无法压缩,最终导致了压缩比不令人满意的结果。
②稀疏格式:经过查阅资料,我们发现了arff格式有一种叫做“稀疏格式”的表示方法。有的时候数据集中含有大量的0值(比如购物篮分析),这个时候用稀疏格式的数据存贮更加省空间。
假设有数据:
@data
0, X, 0, Y, "class A"
0, 0, W, 0, "class B"
用稀疏格式表达的话就是
@data
{1 X, 3 Y, 4 "class A"}
{2 W, 4 "class B"}
4.2 问题的提出
①问题1:稀疏数据只是针对有大量0的情况下,我们现在的缺失值,也就是空值,如何将问题转换?
②问题2:weka所采用的apriori算法与教材上的算法略有不同,简单的说,weka的apriori算法所得出的强关联规则是属性之间的关联,而不是具体属性值的关联。为了说明这个问题,我们做一下测试:
a.假设有以下作者(6人),每条记录表示参与一篇文章写作的作者名;
b.我们使用weka的apriori算法对这些坐着进行关联规则的挖掘。将支持度设置为50%,也就是在以上数据集中存在3条支持项即可满足关联;
c.结果标明L(2)共有2个关联,即Jack、Tom之间存在合作关系,Judy、Jack之间存在合作关系,但实际上Tom、Jack、Lucy之间也符合支持度3的条件。所以weka的apriori算法其实是挖掘属性之间的关联,而不是具体属性值的关联,从上述例子可以很好的看到这一点,即实际是name1、name2、name3、name4之间的关联。
4.3 解决方案
由于存在以上两个问题,原始的数据格式已经无法达成实验预期的目标,因此,我们引入矩阵交集算法思想和垂直数据格式的表示方法。其原理见“实验原理”。其中矩阵交集算法我们只借鉴他的思想,算法实现仍然使用apriori算法。我们的做法是,在不考虑内存问题的情况下,我们将属性设置为所有可能出现的作者名(实际是经过62进制压缩后的标识),故属性个数会达到一百多万个,一条记录为一篇文章,在参与本篇文章写作的对应作者属性那栏置‘1’,其余置为‘0’。使用weka的apriori算法求强关联规则,我们只需关注均为‘1’的关联,忽略0 - 0 ,1 - 0 ,0 - 1 这样的可能。
1. 将数据转换成稀疏表示的垂直数据格式的C/C++主要代码如下:
1 int dblp_end() 2 { 3 ifstream infile1("case_auth.csv" ); 4 ifstream infile2("case9.txt" ); 5 ofstream outfile("dblp_end.csv" ); 6 string *index = new string[NUMOFAUT], 7 *author = new string[120]; 8 string line , temp = ""; 9 int count , n; 10 for(int i = 0 ; i != NUMOFAUT ; ++i) 11 { 12 getline(infile1 , line); 13 index[i] = line ; 14 } 15 outfile << "@relation dblp" << '\n'; 16 for(int i = 0 ; i != NUMOFAUT ; ++i) 17 { 18 outfile << "@attribute " << index[i] << " " << "real" << '\n' ; 19 } 20 infile2.clear(); 21 infile2.seekg(0); 22 outfile << "@data" << '\n'; 23 getline(infile2 , line); 24 for(;getline(infile2,line);) 25 { 26 if(line == "") 27 continue ; 28 n = 0 ; 29 author[0] = ""; 30 for(string::size_type pos = 0 ; pos != line.size() ; ++pos) 31 { 32 if(line[pos] != ',') 33 { 34 author[n] += line[pos]; 35 } 36 else 37 { 38 if(author[n] == "") 39 break ; 40 author[++n] = "" ; 41 } 42 } 43 outfile << '\173' ; 44 count = 0 ; 45 for(int i = 0 ; i != NUMOFAUT ; ++i) 46 { 47 for(n = 0 ; author[n] != "" ; ++n) 48 { 49 if(index[i] == author[n]) 50 { 51 if((count++) != 0) 52 { 53 outfile << ',' ; 54 } 55 outfile << i << " " << 1; 56 break; 57 } 58 } 59 } 60 outfile << '\175' << '\n'; 61 } 62 63 infile1.close(); 64 infile2.close(); 65 outfile.close(); 66 return 0 ; 67 }
2. 转换后的数据:
(属性部分)
(数据部分)
3. 将数据导入到weka。由于垂直数据格式本身的缺点(对内存和CPU的要求高),再者实际数据属性(即出现的作者数)达到一百多万个,因此单台PC难以承担起这个代价,无法通过weka使用apriori算法对这样一个数据集进行处理。
稀疏格式分析:用稀疏格式保存在磁盘上的文件能够有效减少磁盘空间的占有量,但是通过weka处理这些数据时需要先“解压”这些数据,因此对于内存来说,“稀疏”的数据与“不稀疏”的数据没什么差别。
结果分析
由于数据量过于庞大,尤其是稀疏数据集的属性由于为所有出现的作者,因此属性多达一百多万个,用weka导入稀疏数据集一直报错内存不足。由于时间过于紧迫,因此我们放弃了通过weka来挖掘作者间的两两合作关系,而通过直接通过数据库操作对感兴趣的作者合著关系进行挖掘。
初步处理的文件大小为500M,weka无法处理,因而考虑到合作者信息故将所有的单独出现的作者全部删除,这部分的数据不影响最终的结果。处理完成后,数据库中的作者信息并未出现较大的变动,此时考虑对数据进一步的提取。
根据对较大数据集的挖掘规则,可将本数据集中出现较少次数的作者不纳入下一步的考虑范围,此时对数据库的数据进一步提取,将小于110的作者全部删除,这个值是在综合文件大小和数据库内数据出现次数得到的。通过以下程序,实现了对作者数据集属性间关联规则的挖掘以及排序统计过程。
图5.1 挖掘作者数据集的属性间关联规则
图5.2 对挖掘结果(合著者与次数)进行排序统计
由以上程序可以得到一个完整的合作次数排序文件,下图给出最常合作的几个作者信息(详细结果见附件里csv文件):