【前言】

      繼續我們本系列對複雜網路社群結構的方法探索,之前已經嘗試過spark上標籤傳播演算法、igraph 中隨機遊走演算法、networkx中的clique滲透演算法(見筆者相關文章),但一直侷限於無向、無權重圖的分析。本次,向前邁一步,引入權重。選用了igraph中的標籤傳播演算法。

【方法討論】  

    相比於spark上的標籤傳播演算法,發現igraph中的介面增加了對權重的支援,同時不用事先指定迭代的次數,這是兩點好的地方。當然,可以處理的資料量級上,igraph單機版本沒法和spark叢集版本相提並論了。但是相比於python版本networkx的效能瓶頸,igraph C library的計算效率領先幾條街。(小小吐槽一下:igraph同時提供有python、R、C三種版本的介面,相比之下,C library的使用要繁瑣很多,如果不是追求效能稍好些,真心推薦使用其它兩種方式)

   在對權重的處理上,官方文件如是說:

Weights are taken into account as follows: when the new label of node i is determined, the algorithm iterates over all edges incident on node i and calculate the total weight of edges leading to other nodes with label 0, 1, 2, ..., k-1 (where k is the number of possible labels). The new label of node i will then be the label whose edges (among the ones incident on node i) have the highest total weight.

    演算法介面也比較簡潔明確(稍後咱們詳細看如何使用):

int igraph_community_label_propagation(const igraph_t *graph,
                                       igraph_vector_t *membership,
                                       const igraph_vector_t *weights,
                                       const igraph_vector_t *initial,
                                       igraph_vector_bool_t *fixed, 
				       igraph_real_t *modularity);
.

 【不廢話,上完整版程式碼】

#include <stdio.h>
#include <stdlib.h>
#include </usr/local/igraph-0.7.1/include/igraph.h>
#include <string.h>

int main(int argc,char *argv[])
{
    printf("Hello world!\n");
    FILE *edgeListFile;
    FILE *communityWriteFile;
    igraph_t wbNetwork;
    long int i;
    long int no_of_nodes;
    long int no_of_edges;
    int rstCode;
    igraph_vector_t gtypes, vtypes, etypes;
    igraph_strvector_t gnames, vnames, enames;
     /* turn on attribute handling */
    igraph_i_set_attribute_table(&igraph_cattribute_table);

    //初始化物件
    igraph_vector_t membership;
    igraph_vector_init(&membership,0);
    igraph_vector_t weights;
    igraph_vector_init(&weights,0);

    if(argc < 2){
        printf("Usage: %s <inputRelationFile> \n", argv[0]);
        exit(1);
    }

    //邊存放的檔案,空格分隔
    edgeListFile = fopen(argv[1],"r");

    //從檔案中讀入圖
    igraph_read_graph_ncol(&wbNetwork,
                               edgeListFile,
                               NULL,  /*預定義的節點名稱*/
                               1, /*讀入節點名稱*/
                               IGRAPH_ADD_WEIGHTS_YES ,  /*是否將邊的權重也讀入*/
                               0  /*有向圖*/
                               );
    fclose(edgeListFile);

    //警惕:下面這個函式要慎用,第二個引數及最後一個引數有可能會把權重屬性抹掉
    //igraph_simplify(&wbNetwork, 1, 1, 0);

    igraph_vector_init(&gtypes, 0);
    igraph_vector_init(&vtypes, 0);
    igraph_vector_init(&etypes, 0);
    igraph_strvector_init(&gnames, 0);
    igraph_strvector_init(&vnames, 0);
    igraph_strvector_init(&enames, 0);

    igraph_cattribute_list(&wbNetwork, &gnames, &gtypes, &vnames, &vtypes,
			 &enames, &etypes);


    no_of_nodes = igraph_vcount(&wbNetwork);
    no_of_edges = igraph_ecount(&wbNetwork);
    printf("Graph node numbers: %d \n",no_of_nodes);
    printf("Graph edge numbers: %d \n",no_of_edges);

    printf("圖屬性個數: %d \n", igraph_strvector_size(&gnames));
    printf("節點屬性個數: %d \n", igraph_strvector_size(&vnames));
    printf("邊屬性個數: %d \n", igraph_strvector_size(&enames));


    if(igraph_cattribute_has_attr(&wbNetwork,IGRAPH_ATTRIBUTE_EDGE,"weight")){
        //將權重提取到向量中
        EANV(&wbNetwork,"weight",&weights);
        //printf("邊權重屬性值的個數為:%d \n", igraph_vector_size(&weights));
        printf("Edge weight: \n");
        //這裡列印2份是為了驗證從屬性集合中取值與上面所存權重向量的值是否一致
        for(i=0; i<10; i++) {
            printf("Edge weight: %g %g \n",igraph_cattribute_EAN(&wbNetwork,"weight",i),VECTOR(weights)[i]);
        }
    }else{
        printf("The Graph does not have attribute of weight \n");
    }


    rstCode = igraph_community_label_propagation(&wbNetwork,
                                                 &membership,  /*重要:儲存最終每個節點被劃分到的社群編號*/
                                                 &weights,
                                                  NULL,  /*對節點分配的初始化標籤*/
                                                  NULL,  /*是否將一部分節點的標籤固定*/
                                                  NULL   /*最終社區劃分結果的模組度*/
                                                 );

    if(rstCode != 0){
        printf("igraph_community_label_propagation 執行失敗");
        return -2;
    }else{
        printf("Success! \n");
        printf("劃分的社群總數為:%g\n", igraph_vector_max(&membership));
        for(i=0;i<10;i++){
            printf("節點: %d -> 社群:%g \n",i,VECTOR(membership)[i]);
        }
    }

    //將節點劃分的社群結構儲存到檔案中
    communityWriteFile = fopen("/data/tmp/igraph_lp_result.txt","w");
    for(i=0;i<no_of_nodes;i++){
       fprintf(communityWriteFile,"%s\t%g\n",igraph_cattribute_VAS(&wbNetwork,"name",i),VECTOR(membership)[i]);
    }
    fclose(communityWriteFile);

    igraph_vector_destroy(&membership);
    igraph_vector_destroy(&gtypes);
    igraph_vector_destroy(&vtypes);
    igraph_vector_destroy(&etypes);
    igraph_strvector_destroy(&gnames);
    igraph_strvector_destroy(&vnames);
    igraph_strvector_destroy(&enames);
    igraph_vector_destroy(&weights);
    igraph_destroy(&wbNetwork);

    return 0;
}

在我們的測試資料集上,上述程式碼執行結果示意如下,9萬個節點,52萬條邊,瞬間完成。比之前嘗試networkx時9萬節點,30萬條邊就已經抗不住了,這裡的計算效能令人十分滿意。


【要點討論】

  • 從檔案中讀入加權圖

      在上面程式碼中,從一個儲存邊資訊的檔案中讀入圖結構,使用的方法是:

    igraph_read_graph_ncol(&wbNetwork,
                               edgeListFile,
                               NULL,  /*預定義的節點名稱*/
                               1, /*讀入節點名稱*/
                               IGRAPH_ADD_WEIGHTS_YES ,  /*是否將邊的權重也讀入*/
                               0  /*有向圖*/
                               );

注意其中 IGRAPH_ADD_WEIGHTS_YES 這個資訊就表示將邊的權重讀入圖的屬性中,而且千萬要注意接下來不要寫  igraph_simplify(&wbNetwork, 1, 1, 0); 這樣的方法,會把權重資料從邊的屬性中刪除掉,我猜想是最後那個引數把權重抹掉了,暫時也沒時間細究,但這一點真心花了俺好長時間才意識到,因為之前匯入圖時,喜歡使用這個方法再去除多重邊及環啥的,現在先對邊資料檔案預處理好,可以實現簡化。

    同樣一個功能,在R中只需要更加簡單的一句話:

g <- read.graph("your_edge_file.ncol", format="ncol")
  • 處理圖的屬性值  

     如果你認為像上面那樣匯入圖後,就可以開心地處理各種屬性值,你就錯了! igraph C 版本中,圖的屬性處理是最最讓人抓狂的。比如像上面那樣匯入一個圖後,在R中,你就可以直接使用各種屬性了,比如



但是,在c library中,你要至少操作以下幾步:一、建立儲存屬性的各種變數,如下所示:

    igraph_vector_t gtypes, vtypes, etypes;
    igraph_strvector_t gnames, vnames, enames;
     /* turn on attribute handling */
    igraph_i_set_attribute_table(&igraph_cattribute_table);

 二、匯入圖後,要初始化這些屬性變數,如下所示:

    igraph_vector_t gtypes, vtypes, etypes;
    igraph_strvector_t gnames, vnames, enames;
     /* turn on attribute handling */
    igraph_i_set_attribute_table(&igraph_cattribute_table);

 三、屬性又分圖屬性、節點屬性、邊屬性,各有各的方法,比如

檢視各有哪些屬性時,可如此這般:

    printf("Graph attributes: ");
    for (i=0; i<igraph_strvector_size(&gnames); i++) {
      printf("%s (%i) ", STR(gnames, i), (int)VECTOR(gtypes)[i]);
    }
    printf("\n");


    printf("Vertex attributes: ");
    for (i=0; i<igraph_strvector_size(&vnames); i++) {
      printf("%s (%i) ", STR(vnames, i), (int)VECTOR(vtypes)[i]);
    }
    printf("\n");


    printf("Edge attributes: ");
    for (i=0; i<igraph_strvector_size(&enames); i++) {
      printf("%s (%i) ", STR(enames, i), (int)VECTOR(etypes)[i]);
    }
    printf("\n");

本文完整程式碼中檢視是否存在邊的權重屬性時,可如此這般:

igraph_cattribute_has_attr(&wbNetwork,IGRAPH_ATTRIBUTE_EDGE,"weight")

 【結語】

    事實上,不同方法的使用都有相通的地方,上手未必困難,但箇中細節又往往需要花費時間去研究。這也是為何我們堅持把最近嘗試過的一些方法完完整整記錄下相關經驗,以便於同道中人相互交流,減少一些細節點對時間的消耗。如本文有此功效,則幸甚。