아래 예시 코드는 graph_tool 및 matplotlib 등을 사용하여 ers(블록 간 에지행렬), 노드 차수(node degrees), 그리고 fugacities 등을 시각화·분석하는 방법을 보여줍니다.
(사용 편의를 위해 단계별로 주석과 함께 제시하니, 필요한 부분만 골라서 쓰시면 됩니다.)
import graph_tool.all as gt
import numpy as np
import matplotlib.pyplot as plt
###############################
# 1) 네트워크 로드 및 전처리
###############################
# (예시) 정치 블로그 데이터 로드
g_orig = gt.collection.data["polblogs"]
# (a) 가장 큰 연결성분으로 필터링
g_largest = gt.GraphView(g_orig, vfilt=gt.label_largest_component(g_orig))
# (b) 복제 & 단순그래프 만들기 (자기루프/중복간선 제거)
g = gt.Graph(g_largest, prune=True)
gt.remove_self_loops(g)
gt.remove_parallel_edges(g)
print("== After filtering & removing loops ==")
print("Num of vertices:", g.num_vertices())
print("Num of edges :", g.num_edges())
###############################
# 2) 블록 모델 추정
###############################
# 블록모델 추정 (MDL 기반)
state = gt.minimize_blockmodel_dl(g)
# 블록(그룹) 식별자 (각 노드가 어느 블록에 속하는지)
b_array = state.b.a
# 블록 개수 확인
unique_blocks = np.unique(b_array)
print("== Block model info ==")
print("Number of blocks:", len(unique_blocks))
###############################
# 3) ers(블록 간 에지행렬) 확인
###############################
# state.get_bg() : 블록 단위 메타그래프
# state.get_ers(): 블록 간 엣지 수(혹은 가중치)
ers = gt.adjacency(state.get_bg(), state.get_ers()).T
# ers.shape -> (B, B) 형태 (B = 블록 수)
print("== ers (block-block edge matrix) shape:", ers.shape)
# (a) 대각합, 전체합
diag_sum = np.trace(ers) # 대각(블록 내부) 합
off_diag_sum = np.sum(ers) - diag_sum
print(f"Sum of diagonal (intra-block): {diag_sum}")
print(f"Sum of off-diagonal (inter-block): {off_diag_sum}")
# (b) 만약 시각화를 하고 싶다면:
plt.figure(figsize=(6,5))
plt.imshow(ers, cmap='Blues', origin='lower')
plt.colorbar(label='Number of edges')
plt.title("Block-Block Edge Matrix (ers)")
plt.xlabel("Block ID")
plt.ylabel("Block ID")
plt.tight_layout()
plt.show()
# --> 이 행렬을 보고, 대각선 성분이 큰지, 어떤 특정 블록쌍이 유난히 큰지 확인 가능
# --> ex) 대각선이 압도적으로 크다면 "assortative-like" / 비대각이 두드러지면 "bipartite/core-periphery" 가능성.
###############################
# 4) 노드 차수(node degrees) 분석
###############################
# g가 무향 그래프라 가정 시
degrees = g.degree_property_map("out").a # "in"도 같음
print("== Node degree info ==")
print("Min degree:", np.min(degrees))
print("Max degree:", np.max(degrees))
print("Average degree:", np.mean(degrees))
# 차수 분포 히스토그램
plt.figure(figsize=(6,4))
plt.hist(degrees, bins=30, edgecolor='black', alpha=0.7)
plt.xlabel("Degree")
plt.ylabel("Count of nodes")
plt.title("Degree distribution")
plt.tight_layout()
plt.show()
# (a) 만약 누가 허브인지 궁금하다면:
top_k = 5
sorted_idx = np.argsort(-degrees) # 내림차순 정렬 (큰 차수부터)
print(f"Top {top_k} highest-degree nodes:")
for rank in range(top_k):
node_id = sorted_idx[rank]
print(f" Rank {rank+1} node={node_id}, degree={degrees[node_id]}")
###############################
# 5) Fugacities (theta_out, mrs 등) 계산
###############################
# (a) 블록간 실제 에지수(ers), 노드별 out/in degree 가져오기
ers_matrix = ers # 이미 구함
out_degs = g.degree_property_map("out").a
in_degs = g.degree_property_map("in").a # 무향인 경우 out_degs와 동일
# (b) solve_sbm_fugacities 호출
mrs, theta_out, theta_in = gt.solve_sbm_fugacities(b_array, ers_matrix, out_degs, in_degs)
print("== Fugacities (theta) & mrs matrix ==")
print(" mrs shape:", mrs.shape) # (B, B)
print(" theta_out shape:", theta_out.shape) # (N,)
# 만약 무향이면 theta_in == theta_out 비슷할 것
# (c) mrs: 블록 r-s 간 fugacity
# 대략적으로 "r, s 블록 사이 연결 발생 세기"라 보면 됨
# (참고: ers와 mrs는 비례관계지만, 정규화/기대값 설정이 조금 다름)
# 만약 시각화 해보고 싶다면:
plt.figure(figsize=(6,5))
plt.imshow(mrs, cmap='Reds', origin='lower')
plt.colorbar(label='Fugacity (mrs[r,s])')
plt.title("Block-Block fugacity matrix (mrs)")
plt.xlabel("Block ID")
plt.ylabel("Block ID")
plt.tight_layout()
plt.show()
# (d) theta_out: 각 노드별 fugacity
# 이 값이 클수록, 모델에서 "연결을 많이 유발"하는 노드
top_t = 5
sorted_theta_idx = np.argsort(-theta_out)
print(f"Top {top_t} largest theta_out nodes:")
for rank in range(top_t):
node_id = sorted_theta_idx[rank]
print(f" Rank {rank+1} node={node_id}, theta_out={theta_out[node_id]:.4f}, degree={degrees[node_id]}")
# --> 실제 차수와 theta_out 간 비교해보면
# "theta_out 큰 노드 = 차수 큰 노드" 패턴이 보일 가능성 높음.
###############################
# (Optional) 추가 해석
###############################
# - ers와 mrs 모두 (B,B) 매트릭스이므로, 이를 종합해:
# 1) ers 대각선이 큰지/off-diagonal이 큰지
# 2) mrs 대각선이 큰지/off-diagonal이 큰지
# => 어떤 블록들이 자기 내부 연결이 세거나, 어느 블록 쌍이 강력히 연결됐는지
# - theta_out 상위 노드들이, 실제로 네트워크 전체 허브인지 체크.
# - 차수 분포(히스토그램)에서, 파워법 모양인지(직선로그-로그?), 정규형인지, etc.
# - 이런 분석을 통해 네트워크 구조 해석 가능!
ers(블록 간 에지행렬)의 시각화
plt.imshow(ers, ...) 부분에서 파란(또는 다른 컬러맵) 색깔이 진한 블록-블록 쌍은 그 둘 사이에 에지가 많다는 뜻. 노드 차수(degrees) 히스토그램
fugacities
theta_out이 큰 노드는 모델에서 “많은 연결을 일으키는(=고차수)” 노드로 본다는 의미. mrs[r, s]가 큰 블록 쌍은 “연결 강도가 크다” → 해당 블록 쌍에서 에지가 자주 생김.추가로
b_array를 활용해, “같은 블록 노드들만 추출”하여 서브그래프 시각화 등도 가능. 위 코드를 실행하면,
아래 튜토리얼은 (\texttt{graph_tool.generation.solve_sbm_fugacities}) 함수가 무엇을 하는지, 그리고 이를 어떻게 활용하여 SBM(확률적 블록 모델)의 'fugacity'(퓨가시티, 일종의 라그랑주 승수와 유사한 개념)를 효율적으로 추정할 수 있는지 알려주는 “초보자를 위한 가이드”입니다.
ers)와 각 노드의 기대 차수(out_degs, in_degs)가 주어졌을 때, ( \text{fugacity} )들을 계산하는 방법을 이해하고, 실제 코드를 통해 다뤄보기.solve_sbm_fugacities(...) 함수의 의도b: 각 노드가 어느 블록에 속하는지 (길이 (N)의 배열) ers: 블록-블록 간 “예상(평균) 엣지 수” 행렬 ((B\times B)) out_degs / in_degs: 각 노드의 “예상 out-degree” / “예상 in-degree” in_degs 없이도 됨(동일하다고 간주). multigraph, self_loops: 멀티그래프인지, 자기 루프 허용인지 등. mrs: 블록-블록 간 fugacity 행렬 ((B\times B)), out_theta, (옵션) in_theta: 각 노드별 fugacity(길이 (N)). b_array)이 정해져 있고, 블록-블록 간 엣지 개수 기대치((ers))와 노드별 차수(기대치)가 주어져 있을 때, 최대 엔트로피 SBM(또는 Poisson SBM)에 필요한 파라미터((\lambda_{rs}) 등)를 추정하고 싶다.문서에 따르면, 단순그래프(undirected)일 때나 유향/멀티그래프일 때, 수학적 자기일관성 식은 각각 다릅니다.
이 함수는 내부적으로 “이 방정식을 만족”하도록 (\thetai)와 (m{rs})를 찾아주는 과정을 진행합니다.
문서에서 발췌 & 정리:
b[i]는 노드 (i)가 어느 블록(r)에 속하는지. ers[r,s] = 그룹 (r)-(s) 사이에 평균 몇 개의 엣지(또는 (r=s)면 내부엣지 2배 계산) in_degs=None으로 둬도 됨. minimize/root solver 쓸 수도 있음(하지만 보통 True). 아래는 “가정된 예시 데이터”를 이용해 튜토리얼 형태로 작성한 코드 예시입니다.
주요 흐름:
1. 임의의 블록 분할 (b), 블록 엣지행렬 ers, 노드 차수 out_degs 등을 준비
2. solve_sbm_fugacities(...)로 (mrs, theta_out, theta_in) 계산
3. 결과를 해석
import numpy as np
import graph_tool.all as gt
def demo_solve_sbm_fugacities():
"""
demo_solve_sbm_fugacities:
- 임의의 블록 구조, ers, 노드 차수 등을 준비해,
solve_sbm_fugacities 함수를 호출해본다.
"""
# 1) 블록 분할 (예: 노드가 6개 있다고 가정)
b = np.array([0, 0, 1, 1, 2, 2], dtype=int)
# => 6노드를 3개 블록(0,1,2)로 나눈 예시
# 2) ers(블록 간 에지 기대치) 준비
# B=3 블록, ers[r,s] = 그룹r-s의 예상 엣지 수
# 예: 대각성분은 블록 내부엣지
ers_data = np.array([
[10.0, 3.0, 1.0],
[ 3.0, 12.0, 2.0],
[ 1.0, 2.0, 5.0]
])
# => r=0 블록 내부 10개, r=1 블록 내부 12개 등
# 3) out_degs (노드 차수) 준비
# 여기서는 6노드 각각 임의로 설정
out_degs = np.array([5, 4, 6, 6, 2, 3], dtype=float)
# in_degs: 무향 그래프라면 None으로도 가능
in_degs = None
# 4) solve_sbm_fugacities 호출
mrs, theta_out = gt.solve_sbm_fugacities(
b, ers_data, out_degs, in_degs,
multigraph=False, # 단순그래프라고 가정
self_loops=False, # 자기루프X
epsilon=1e-8,
iter_solve=True, # 단순 반복법
verbose=True
)
# => in_degs=None 이므로 반환값은 (mrs, theta_out)
# in_degs != None 이면 (mrs, theta_out, theta_in) 세 개를 반환
print("[결과] mrs shape:", mrs.shape) # (3,3)
print("[결과] theta_out shape:", theta_out.shape) # (6,)
# 5) 결과 해석
# mrs: (B,B) sparse matrix. 필요하다면 .toarray()로
mrs_dense = mrs.toarray()
print("[결과] mrs (dense):\n", mrs_dense)
# theta_out: 노드별 퓨가시티 => 높을수록 엣지를 많이 생성하려는 경향
for i, val in enumerate(theta_out):
print(f" Node {i}, theta_out={val:.4f}, expectedDeg={out_degs[i]}")
if __name__ == "__main__":
demo_solve_sbm_fugacities()
[결과] mrs shape: (3, 3)
[결과] theta_out shape: (6,)
[결과] mrs (dense):
[[ 1.2839 0.7542 0.1031]
[ 0.7542 1.4943 0.2205]
[ 0.1031 0.2205 0.7127]]
Node 0, theta_out=0.5332, expectedDeg=5.0
Node 1, theta_out=0.4928, expectedDeg=4.0
Node 2, theta_out=0.6211, expectedDeg=6.0
Node 3, theta_out=0.6026, expectedDeg=6.0
Node 4, theta_out=0.2113, expectedDeg=2.0
Node 5, theta_out=0.3201, expectedDeg=3.0
(단, 위 수치는 가상의 예시로서, 실제와 다름.)
mrs[r, s](블록 간 fugacity): theta_out[i](노드 i의 fugacity): theta_out[i]도 큼. solve_sbm_fugacities가 이 식을 만족하도록 (\theta, m)들을 찾는다. b는 “노드 i가 속한 블록”을 나타내는 배열. 즉, 0부터 (B-1)까지 정수 값을 가져야 하며, 블록 수가 몇 개인지와 일치해야 한다. ers의 shape: in_degs가 None인지 아닌지에 따라 반환값이 달라짐. (유향 vs 무향) epsilon, max_iter 조절. generate_maxent_sbm 함수를 통해 샘플 그래프를 만들거나, 혹은 모델 해석(고차수 노드, 특정 블록들 연결성) 등 다양한 분석을 할 수 있습니다.추천 후속 학습
solve_sbm_fugacities 수행 → “원본과 비슷한 확률모형” 만들어 보기.