Boostとは...

C++ 用のライブラリ.汎用的なライブラリの少なかった? C++ にとって,これのおかげで開発がしやすくなったといっても過言ではない.たぶん.ここではBoostに関する詳しい説明は省くので,下記の参考書などを参考にしてもらいたい.

なぜ Boost/graph を利用するのか?

本ページでは,Boostを用いたネットワークの中心性の計算を方法について説明する.

ネットワークの中心性とは,ある要素集合と,個々の要素間の関係性から構成されるネットワークにおいて,そのネットワークにおける各要素の重要性を示す指標として,様々なものが提示されている.このネットワークの中心性は,ネットワーク分析や複雑系ネットワークにおいて重要な要素であるが,私自身が始めた当時,大規模なネットワークを対象としたライブラリやツールは提供されていなかった.そこで,実際にネットワークデータをプログラムで扱おうと作成しはじめて見たところ,データ構造や管理,探索など結構面倒であった.scale-free なネットワークを作成する程度であれば,比較的単純なコードで作成できるが,中心性の計算などそれ以上のことをすると,とたんに配列計算など面倒な作業が伴ったため.既存のライブラリを探していたときに見つけたのが,この boost の graph ライブラリである.

当時,この boost/graph は,あくまでもグラフ用のライブラリであったこともあり,ネットワーク分析のための関数などは乏しいうえに,ライブラリ自体,多少癖が強くなかなか使いづらかった.簡単な作業であればサンプルを参考にすれば作成できるが,資料なども少ない状態で,下記の洗練されたプログラムとはいえないが,とりあえず、公開しておく.参考になったらさいわいである.

なお,最近では R の様な統計処理ツールでも SNA という社会ネットワーク処理用パッケージが提供されているらしいので,そちらの方が便利な可能性もある.あと,ネットワークデータの形式として graphviz 形式を利用しているが,そのデータ形式を読み込むバイナリライブラリのなまえが 1.34.0 以降変わってしまってから動作が確認できていないので注意されたい.


ネットワークデータクラスの準備

グラフデータの定義

近接ノードのリストとしてネットワークデータを定義.一列目の三つは,ノードのデータ型,エッジのデータ型,有向 or 無向グラフかの指定で,その後はネットワーク,ノード,エッジのプロパティ定義と成っている.プロパティ定義では,テレコな定義となっていて, > は,空白をキチンとあける必要がある.

typedef adjacency_list <vecS, vecS, undirectedS, property <vertex_name_t, int, property <vertex_index1_t, float, property <vertex_distance_t, int, property <vertex_discover_time_t, float, property <vertex_degree_t, int, property <vertex_priority_t, float> > > > > >, property <edge_name_t, std::string, property <edge_weight_t, int> >, GraphvizGraphProperty> SocialNetwork;

上記の定義は、おそらく使っていないプロパティもあり無駄がおおい.だが,複数のプログラムで共通して使っているため,どこでどれをつかっているかわからなくなっているために,現状のまま使用している.

深さ優先探索用の visitor. といっても,サンプルをそのまま流用していて,これ以外のやり方がわかっていないというのが正しい.....


  /**
   * グラフ探索用 visitor
   */
    template 
    class average_distance_visitor : public default_bfs_visitor {
    
    public:
      average_distance_visitor(DistanceMap dist) : d(dist) {}
      
      template  
      void tree_edge(Edge e, const Graph& g) const {
	d[target(e,g)] = d[source(e,g)] + 1;
    }
    private:
      DistanceMap d;
    };
    template 
    average_distance_visitor distance_recorder(DistanceMap d) {
      return average_distance_visitor(d);
    };
	       

クラスタリング係数の計算

クラスタリング係数(Clustering coefficient)とは,あるノードの近接ノード同士が互いに近接ノードであることと定義されている.簡単に説明するときには,自分の友達が友人同士であるという言葉が使われることが多い.この値が大きいコンポーネントほど,集団が密であるといえるが,係数が1に近いとしてもそれは必ずしも集団の中に壁がないこととは同義ではない.逆に0に近いときには木構造もしくは4近傍格子空間のような構造となっている.

このクラスリング係数では第1近傍までしか考慮しないが,第2近傍まで考慮する係数があるのかは不明.


  ScoreTable c;                  // ノードに対応した vector 配列
  // g は adjacency_list, ノード集合のiteratorを取得してfor文で回す
  for(tie(vi,vi_end) = vertices(g); vi != vi_end; ++vi) { 
    AIter ai, ai_end, ai2;
    tie(ai, ai_end) = adjacent_vertices(*vi, g);           // *vi の近接ノードを取得
    float adjacent_num = (float)std::distance(ai, ai_end); // 近接ノードの数を求める
    c[*vi] = 0.0;
    for(; ai != ai_end; ++ai) {     //近接ノード同士にリンクがあるかどうかを調べる
      ai2 = ai + 1;
      for(; ai2 != ai_end; ++ai2) {
	if(edge(*ai, *ai2, g).second == true) // リンクがあるなら
	  c[*vi] += 2.0;                      // 双方向リストとして計算
      }
  }
  // 平均を求める
  float meanClustringCoefficient = 0.0;
  for(tie(vi, vi_end) = vertices(g); vi != vi_end; vi++) {
    meanClustringCoefficient += (c[*vi] / (float)num_vertices(g));
  }
  cout << meanClustringCoefficient << " ";
	       

Shortest Path Lengthの計算


  for(tie(vi, vi_end) = vertices(sn); vi != vi_end; ++vi) {
    DistTable dist(num_vertices(sn),0);
    // ノードからの距離を計算
    breadth_first_search(sn, *vi, visitor(distance_recorder(&dist[0])));
    VIter it, it_end;
    int count = 0;
    // 距離の総和を計算
    for(tie(it, it_end) = vertices(sn); it != it_end; ++it) {
      if(*vi != *it && dist[*it] == 0) // 接続していないノードはノードの総数を距離として計算
	count += num_vertices(sn);
      else
	count += dist[*it];
    }
    score[*vi] = (float)count / (float)(num_vertices(sn) - 1);
  }
	       
ネットワーク中心性の計算

Closenessの計算


  ScoreTable L(num_vertices(g));
  L = calc_L(g);                      // shortest path length を計算  
  for(tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) { // 逆数の総和を求める
    score[*vi] = 1 / L[*vi];
  }  		   
		 

PageRankの計算

PageRankの計算は,厳密にはことなるが基本として Eigenvector Centralityの計算に近い.この計算では,結合した集団の中でつながりを通じて確率的に移動する粒子を仮定して,その集団内での粒子の存在確率を求める指標である.つまり,この値が高いノードとは,Webサイトでいうならば訪問者が多いだろうということになります.PageRankの場合は,リンクのみの移動制約を外してランダムに移動可能な粒子の割合を仮定しています.


  // 各ノードに初期値(1/N)を配布する. 
  ScoreTable score(num_vertices(g), 1.0 / (float)num_vertices(g));
  while(true) {
    float cache = 0.0;
    ScoreTable nv(num_vertices(g), 0.0); // ランダム移動の計算のためのバッファ
    for(tie(vi, vi_end) = vertices(g); vi != vi_end; vi++) {
      float num = (float)out_degree(*vi, g);
      // 自身の現在持っている値の0.85を近接ノードに均等に割り振る
      if(num == 0.0)
	cache += score[*vi];
      else {
	AIter ai, ai_end;
	for(tie(ai, ai_end) = adjacent_vertices(*vi, g); ai != ai_end; ai++)
	  nv[*ai] += (score[*vi] * 0.85) / num;
        // 均等配分をキャッシュにためる
	cache += score[*vi] * 0.15;
      }
    }
    float total = 0.0;
    for(tie(vi, vi_end) = vertices(g); vi != vi_end; vi++) {
      nv[*vi] += (cache / (float)num_vertices(g));
      // 全体の変化量をひろう
      total += (nv[*vi] - score[*vi]) * (nv[*vi] - score[*vi]);
      score[*vi] = nv[*vi];
    }
    if((total * 1000.0) < 0.0000001)  // 変化が乏しくなったら収束したとする
      break;
  }
  return score;		   
	         

Betweennessの計算

Betweenness Centrality は,あるノードからの距離をはかり,一番遠くにあるノードから,近接ノードのなかで距離の近いところにあるノードに水を流すように値を渡していく.これで,ほぼ,nm (nはノード数,mはエッジ数)に計算量が押さえられるはず.といっても,経路の数を数えるために 2nm になっているが.


  for(tie(vi, vi_end) = vertices(g); vi != vi_end; vi++) {
    if(out_degree(*vi, g) == 0 || (int)out_degree(*vi, g) == (int)(num_vertices(g) - 1))
      continue;
    DistTable d(num_vertices(g), 0);
    // 対象とするノードから全てのノードへの距離を測るための探索
    breadth_first_search(g, *vi, visitor(distance_recorder(&d[0])));
    int diameter = 1;
    // 距離の遠い順にノードを並べる
    for(tie(vi2, vi2_end) = vertices(g); vi2 != vi2_end; ++vi2) {
      if(*vi == *vi2 || d[*vi2] == 0)
	continue;
      if(diameter < (int)d[*vi2]) {
	diameter = (int)d[*vi2];
	break;
      }
    }
    if(diameter > 1)      
      check_betweenness(1.0, d, b, g, diameter); // betweennessの計算
  }
  for(tie(vi, vi_end) = vertices(g); vi != vi_end; vi++) {
    score[*vi] = b[*vi]/ ((((float)num_vertices(g)) * ((float)num_vertices(g) - 2.0)) / 2.0);
  }
		 

check_betweenness(float value, DistTable d, ScoreTable& b,
			const SocialNetwork sn, int diameter) {
  AIter ai, ai_end;
  map betweenness;
  map d_table;       //最短パス長が同じノードのリスト

  VIter it, it_end;
  for(tie(it, it_end) = vertices(sn); it != it_end; it++){
    if(d[*it] < 1)                   //同じ距離にあるノードをまとめる作業
      continue;
    d_table[d[*it]].push_back(*it);
    if(diameter < (int)d[*it]) {
      diameter = (int)d[*it];
    }
  }
  for(int i = diameter; i > 1; i--) { //
    VerticesL vertices = d_table[i];
    while(!vertices.empty()) {
      Vertex v = vertices.front();
      int num = 0;
      // 条件(現在のノードの最短パス長より短いノード)にあう近接ノードの数を求める		
      for(tie(ai, ai_end) = adjacent_vertices(v, sn); ai != ai_end; ai++)
	if(d[v] > d[*ai])
	  num++;
      // 現在のノードがもつ betweenness の値に1を足して num で割った値を追加する
      for(tie(ai, ai_end) = adjacent_vertices(v, sn); ai != ai_end; ai++) 
	if(d[v] > d[*ai]) {
	  betweenness[*ai] += (betweenness[v] + 1.0) / (float)num;
	}
      vertices.pop_front();
    }
  }
  for(tie(it, it_end) = vertices(sn); it != it_end; it++) {
    b[*it] += betweenness[*it];
  }		   
		 

betweenness の計算に関しては,ライブラリが用意されており,こちらでは edge_betweenness なども計算できるので便利?

Boost/BetweennessCentrality

boostライブラリにあるメソッドを利用して betweenness を計算するための準備

#include 
#include 
#include 
#include 		   
		 

上三つはこれまでと同じ

続いてノードの定義とグラフクラスの準備


struct Node : public std::vector {
  int id;
  enum Type { Exit, Junction } type;
  Node(int _id) : id(_id), type(Junction) {}
  bool remove(Node*);
 private:
  Node();
};

typedef std::vector nodes_t;

class Graph : public std::map {
 public:
  Graph(std::istream&);
  ~Graph();
  const nodes_t& getExits() const { return exits; }
  bool remove(Node*);

  void dump(std::ostream&) const;

 private:
  Graph();
  Node* allocate(int);

  nodes_t exits;
  std::vector nodes;
};
		 

つづいて,betweenness の計算.

いろいろやっているが,見たところ,近接関係グラフ (adacency list)のデータを基に計算をして, associative_property_map に結果を放り込んでいるだけの様子.


void betweeness(const char* filename, Graph& graph) {

  using namespace boost;
  typedef adjacency_list< vecS, vecS, undirectedS > BoostGraph; //グラフの定義
  const int V = graph.size();

  std::map idMap = id_to_pos(graph);
  BoostGraph bgraph(V);

  // Graph のデータを 	       
  for (Graph::iterator sit = graph.begin(); sit != graph.end(); ++sit) {
    Node *node = sit->second;
    // 近接ノード取りだし			      
    for (Node::const_iterator nit = node->begin(); nit != node->end(); ++nit) {
      const Node* other = *nit;
      if (other->id > node->id) {
	const int fpos = idMap[node->id];
	const int tpos = idMap[other->id];
	add_edge(fpos, tpos, bgraph);
      }
    }
  }

  std::map betweenness;
  boost::associative_property_map< std::map >  betweenness_properties(betweenness);  

  // brandes の betweenness の計算		 
  brandes_betweenness_centrality(bgraph, betweenness_properties);
  // 結果の表示	 
  for (Graph::iterator sit = graph.begin(); sit != graph.end(); ++sit) {
    Node *node = sit->second;
    const int fpos = idMap[node->id];
    debug(3) << node->id << "\t" << betweenness[fpos] << "\n";
  }
}
	         

なお,上記のcodeは,副田さんから提供いただいたものに注釈を加えたものです.

参考資料
プログラム

graphviz 形式のネットワークデータから,Closeness, Betweeness, PageRank, 次数分布を出力するプログラムです.このプログラムをコンパイルするには,Boostライブラリを必要とします.

また,ノードごとの中心性の値を出力しますが,ノードのIDに関しては,サボっているため,graphviz データで読み込んだノードの順番になっています.修正などができたらおしえていただけるとさいわいです.