【網絡博弈】理解與實現基於網絡博弈的動力學數據生成


復雜網絡與網絡博弈

本文省略大量預備知識,並以極簡化的方式介紹網絡博弈和演化動力學。

常見三種復雜網絡

復雜網絡本質是一種高連接屬性的拓撲結構。在網絡上進行博弈過程,網絡結構本身對博弈過程會產生影響。

常用的三種網絡有:ER隨機網絡(Erdős Rényi Random)、BA無標度網絡(Barabási Albert Scale-free)、小世界網絡(Small-world)。

ER網絡和SW網絡屬於均質網絡,ER網絡中每個節點的度均相同,收益信息在網絡上的傳導同樣快,用於研究合作行為在“人人平等”的情況下演化的過程。而BA無標度網絡屬於異質網絡,其特點是度分布滿足冪律特征,hub節點所傳導的信息比一般節點更多。SW網絡充分體現“六度分隔”現象,常用於研究合作行為在小世界現象下如何演化。

通過特定參數下的模擬,這三種拓撲結構基本如下圖:

所有圖的規模N = 50,ER網絡的連邊概率p = 0.01;SW網絡中每個節點的鄰居數量nei = 3,重連接概率為p = 0.2;SF無標度網絡的平均度<k> = 6。

網絡結構上,視每個節點為一個博弈主體,而將每條邊視為玩家間的直接聯系,這樣的種群被稱為結構種群。

具有收益矩陣的常見網絡博弈

網絡博弈多種多樣,我們能在不同的博弈類型中得到合作行為演化的不同軌跡。常見的博弈框架根據收益計算的不同基本分為具有收益矩陣的、非收益矩陣表達的。
這里討論三種具有單邊收益矩陣的兩人博弈,和一種非矩陣表達的博弈,分別為:

  • 囚徒困境(PDG)
  • 雪堆博弈(SDG)
  • 捐贈博弈(DG)
  • 最后通牒(UG)

PDG、SDG、DG 的單邊收益矩陣,UG 的收益計算形式分別表示為:

上圖中的參數 b 和 c 通常用來控制困境強度,以便更好的研究不同程度的困境將如何影響合作行為的演化,通常將 c 設置為 1,控制 b 值從大到小逐步變化。
其中最后通牒博弈中的 (\(p_i\), \(q_i\)) 代表第 i 個 player 的策略,一般習慣在 0-1 上取值,當 i 與鄰居j進行博弈時,\(p_i\) 表示個體 i 最多能提供給鄰居的金額,\(q_i\) 表示個體 i 最少能接受鄰居提供的金額。

上述四種博弈均只討論兩玩家的情形。
當重復博弈進行一輪時,個體 i 通過網絡連邊中的直接連接關系(鄰接矩陣中有連邊的部分),可以分別計算出它與所有鄰居 j 博弈所得的收益,進而通過加總全部收益得到累計收益(Total Payoff)。
定義 M 為博弈矩陣,f 為博弈所帶來的收益,S 表示個體選擇的策略,這里個體間的策略我們抽象地概括為親社會性的 “C” 策略、自私性的 “D” 策略。另外記 G 為結構種群。
則S可以表示為:

\[S=\begin{cases} (1, 0),\quad if \quad C \\ (0, 1),\quad if \quad D \end{cases} \]

初始狀態下,每個個體的策略隨機給定 C or D,進一步的,定義在網絡 G(V,E) 上全部與個體 i 的鄰居集合為 \(\Omega_i\) 。則收益計算可以表示如下:

\[M(G \otimes S) \rightarrow f \]

具體在計算時實際上是個體 i 的策略向量與個體 j 的策略向量通過收益矩陣 M 進行向量乘法運算,可以表達為:

\[f_i = \sum_{j \in \Omega_i} S_i^T M S_j \]

然而分別對每一個體i反復迭代運算無形中增加計算負擔,於是根據原理,可以利用鄰接矩陣對上式重新定義,首先定義鄰接矩陣 A:

\[A = \{ a_{ij} \}_{N \times N} \]

\[a_{ij} = \begin{cases}1,\quad (v_i, v_j) \in E \\ 0,\quad (v_i, v_j) \notin E \end{cases} \]

再記 \(\Phi_{ij}\) 為全部 \(S_i^T M S_j\) 構成的矩陣,則直接將 \(\Phi_{ij}\) 與鄰接矩陣做 Hadamard 積(對應位置相乘),i 與 j 不相連的項全部乘零消失,留下的項均為有連邊的收益計算結果,收益矩陣表示為:

\[F = A \odot \Phi_{ij} \]

自然的,個體 i 的累計收益表示為 F 的第 i 行或列求和即可:

\[f_i = \sum{F_{Ni}} = \sum{F_{iN}} \]

根據上述步驟進行一輪博弈,計算出全部個體的累計收益向量后需要更新每個個體的初始策略。常見的動力學更新規則有:

  • 模仿更新
  • 從眾更新
  • 期望更新
  • 自反更新

這里的只采用 Fermi 更新,一種簡單的模仿更新。該模仿規則能夠根據個體收益差計算出個體 i 的策略互換概率 w,即:

\[w = \dfrac{1}{1+\exp(\Pi_i - \Pi_j)/\kappa} \]

其中 \(\kappa\) 可以理解為決策時產生的擾動,一般取很小的值,例如0.01。

Fermi 更新在通過概率 w 決定是否要與對手互換策略時,如果個體 i 有多個鄰居之間相連,則在滿足 w 條件時,需要先選擇其中一個鄰居進行策略互換,這里無論策略好壞(所帶來的收益高低),每個鄰居被抽到的概率均等。

Fermi 在更新過程中也可以遵循兩種不同的動力學規則,即同步更新或者異步更新:

  • 同步更新是指一輪博弈結束后,所有 N 個節點需要順序遍歷,也就是每個節點都需要進行且只進行一次更新。

  • 異步更新指的是系統總共需要迭代 N 次(節點個數次),每次更新的對象從 N 個個體中等概率抽樣,也就是最極端的情況下可能有節點從未更新過,或者有節點重復更新 N 次。

事實上進行博弈的過程中你會發現,隨着博弈重復進行下去,在特定的更新機制下復雜系統中個體策略將不斷變化,直到一定步長時,C 策略和 D 策略各占據一定比例不再改變(非理想狀態下應該圍繞某個比例小幅度波動)。

動力學數據生成實現

生成的數據主要是累計收益向量 \(Y = \sum{F_{N \cdot}}\),以及每輪博弈的矩陣\(\Phi_{ij}\),即有多少輪模擬就會有多少列Y,有多少個節點就會有多少個 \(\Phi_{ij}\)

原創,僅供自己回憶使用,請勿轉載

For R

gen_dynamic<-function(N, L, aver_k=6, net_type="BA", game_type="PDG"){

  # Calculate the payoff between i and j
  # which need Payoff Matrix(for others except SUG)
  payoff<-function(si, sj, M){
    return(t(as.matrix(si)) %*% M %*% as.matrix(sj))
  }
  # which no need Payoff Matrix(for SUG)
  payoff_sug<-function(s_i, s_j, S){
    p_i<-S[,s_i][1]; q_j<-S[,s_j][2]; p_j<-S[,s_j][1]; q_i<-S[,s_i][2]
    if ((p_i >= q_j) & (p_j >= q_i)) {p_sug<-p_j + 1 - p_i}
    if ((p_i >= q_j) & (p_j < q_i)) {p_sug<-1 - p_i}
    if ((p_i < q_j) & (p_j >= q_i)) {p_sug<-p_j}
    if ((p_i < q_j) & (p_j < q_i)) {p_sug<-0}
    return(p_sug)
  }
  
  # Generate different Game structure except SUG
  gen_PDG<-function(m.R, m.S, m.T, m.P){
    return(matrix(c(m.R, m.S, m.T, m.P), 2, 2, byrow=T)) # PDG Payoff Matrix
  }
  gen_SDG<-function(m.r){
    return(matrix(c(1, 1-m.r, 1+m.r, 0), 2, 2, byrow=T)) # SDG Payoff Matrix
  }
  gen_DG<-function(m.b, m.c){
    return(matrix(c(m.b-m.c, -m.c, m.b, 0), 2, 2, byrow=T)) # DG Payoff Matrix
  }
  
  ################################### Simulation of Dynamics ############################################
  # ER and SW networks are transformed into degree control
  p_or_m<-function(N, k){
    return(round(k / (N-1)))
  }
  
  # Generate different Complex Networks
  if(net_type == "BA"){G<-barabasi.game(n=N, power=1, m=aver_k, directed = F)} # BA Scale-Free
  if(net_type == "ER"){G<-erdos.renyi.game(n=N, p.or.m=p_or_m(N, aver_k), type="gnp", directed = F)} # ER Random
  if(net_type == "SW"){G<-sample_smallworld(dim=1, size=N, nei=aver_k/2, p=.0001)} # SW Regular
  
  # Init Payoff Matrix
  if(game_type == "PDG"){M<-gen_PDG(1, 0, 1.2, 0)} # R、S、T、P參數隨意設置
  if(game_type == "SDG"){M<-gen_SDG(1.2)} # r參數隨意設置
  if(game_type == "DG"){M<-gen_DG(2, 1)} # b、c參數隨意設置
  
  # Init Strategy
  if(game_type == "SUG"){
	# for SUG
    S<-as.data.frame(matrix(NA, 2, N))
    for (i in 1:N) {
      S[,i]<-c(runif(1), runif(1))
    }
  }else{
    # For others
    S<-as.data.frame(matrix(NA, 2, N))
    for (i in 1:N) {
      if(runif(1) >= .5){
        S[,i]<-c(0, 1)
      }else{
        S[,i]<-c(1, 0)
      }
    }
  }
  
  # Record Adj
  real_adj<-as.matrix(get.adjacency(G))
  storage.mode(real_adj)<-"integer" # 因為Adj全是0-1,所以強行由double型儲存為integer型
  
  # init X, Y
  Y<-data.frame(matrix(NA, L, N)) # 每一列為一個時間步的累計收益
  X<-list() # 為包含N個矩陣的列表(即分塊對角矩陣分塊存儲)
  for (i in 1:N) {
    X[[i]]<-matrix(NA, L, N)
  }
  
  # simulation process
  for (l in 1:L) {
    F_temp<-matrix(NA, N, N)
    if(game_type == "SUG"){
      for (u in 1:N) {
        for (v in 1:N) {
          F_temp[u, v]<-payoff_sug(u, v, S)
        }
      }
    }else{
      for (u in 1:N) {
        for (v in 1:N) {
          F_temp[u, v]<-payoff(S[,u], S[,v], M)
        }
      }
    }
    
    # Record Y and X
    F_cumulate<-apply((real_adj*F_temp), 1, sum)
    # F_cumulate<-apply((real_adj*F_temp), 2, sum)
    Y[l, ]<-F_cumulate # The row of Y
    for (n in 1:N) {
      X[[n]][l,]<-F_temp[n,]
    }
    
    # Fermi Updating (Synchronization Update)
    if(game_type == "SUG"){
      for (i in 1:N) {
        update_index<-sample(as.vector(neighbors(G, i, "all")), 1)
        w<-1/(1 + exp((F_cumulate[i] - F_cumulate[update_index])/.1))
        if(runif(1) >= w){
		  S[, i]<-S[, update_index]
          S[, i]<-S[, i] + .05
        }
      }
    }else{
      for (i in 1:N) {
        update_index<-sample(as.vector(neighbors(G, i, "all")), 1)
        w<-1/(1 + exp((F_cumulate[i] - F_cumulate[update_index])/.1))
        if(runif(1) >= w){
          S[, i]<-S[, update_index]
        }
      }
    }
  }
  # X block diag treatment
  # X_diag_block<-as.matrix(bdiag(X)) #使用此代碼將X保存為分塊對角矩陣需要用到開頭備注的 “bigmemory” 包
  return(list(Y = Y, X = X, real_adj = real_adj))
}

# dyn_vars<-gen_dynamic(100, 15, 6, "BA", "SUG")
# dyn_vars[["Y"]]; dyn_vars[["X"]]; dyn_vars[["real_adj"]]

For Python

import numpy as np
import math, random
import networkx as nx

# Gen Network
class Gen_Net:
    def basf(n: int, k: int):
        G_sf = nx.barabasi_albert_graph(n, k)
        return G_sf
    def ER(n: int, p: float):
        G_er = nx.random_graphs.erdos_renyi_graph(n, p)
        return G_er
    def SW(n: int, k: int, p: float):
        G_sw = nx.random_graphs.watts_strogatz_graph(n, k, p)
        return G_sw

# Define Useful Tools
class Use_Tools:
    def chunk(lst, size): # make selected array sort with rule size
        return list(map(lambda x: lst[x * size: x * size + size], list(range(0, math.ceil(len(lst) / size)))))
    def payoff(si, M, sj) -> float: # si*M*sj' and input is np.array
        phi = np.matmul(np.matmul(si, M), sj.T)
        return phi

# Define Game Structure
class Game:
    def PDG(b: float):
        M = np.array([[1, 0], [b, 0]])  # b = 1.2; M[0][0]=1; M[0][1]=0; M[1][0]=1.2; M[1][1]=0
        return M
    def SDG(r: float):
        M = np.array([[1, 1 - r], [1 + r, 0]])
        return M

# Initalize Strategies Set
G = Gen_Net.basf(100, 6) # average degree <k> = 6: k = 3
node_list = np.array(list(G.nodes)) + 1 # all nodes
edge_list = np.array(G.edges) + 1 # linkage's edges
nebmat = nx.to_numpy_matrix(G) # Adjacent Matrix of G
# np.savetxt('Adjacent_Matrix.csv', nebmat, delimiter = ',')

s_set = [] # every node which choose C or D with equal prob
for _ in range(len(node_list)):
    if np.random.rand(1) > 1 / 2: s_set.append((0, 1))
    else: s_set.append((1, 0)) # strategies set of #N nodes

# Monte-Carlo Simulation
L = 300; Phi = []
M = Game.PDG(1.2) # MC Rounds
Y = np.zeros([L, G.number_of_nodes()])
for _ in range(G.number_of_nodes()): # Store Phi
    Phi.append(np.zeros([L, G.number_of_nodes()]))

for l in range(L):
    for f in range(G.number_of_nodes()): # asynchronous updating
        temp_payoff, neb_temp_payoff = [], []

        focal_player = random.sample(list(node_list), 1)[0]
        # print("\n", focal_player)

        # find focal's neb and calculate payoff of focal player
        rdn_neb = []
        for i in range(len(edge_list)):
            if edge_list[i][0] != focal_player:
                if edge_list[i][1] == focal_player:
                    rdn_neb.append(edge_list[i][0])
                    temp_payoff.append(Use_Tools.payoff(np.array(s_set[edge_list[i][1] - 1]), M, np.array(s_set[edge_list[i][0] - 1])))
            else:
                rdn_neb.append(edge_list[i][1])
                temp_payoff.append(Use_Tools.payoff(np.array(s_set[edge_list[i][0] - 1]), M, np.array(s_set[edge_list[i][1] - 1])))
        # print("\n", rdn_neb)
        # print("\n", temp_payoff)
        f_i = np.sum(temp_payoff)
        Y[l][focal_player - 1] = f_i

        # find neb's neb and calculate payoff of player's neb
        select_neb = random.sample(rdn_neb, 1)[0]
        for i in range(len(edge_list)):
            if edge_list[i][0] != select_neb:
                if edge_list[i][1] == select_neb:
                    neb_temp_payoff.append(Use_Tools.payoff(np.array(s_set[edge_list[i][1] - 1]), M, np.array(s_set[edge_list[i][0] - 1])))
            else:
                neb_temp_payoff.append(Use_Tools.payoff(np.array(s_set[edge_list[i][0] - 1]), M, np.array(s_set[edge_list[i][1] - 1])))
        # print("\n", select_neb)
        # print("\n", neb_temp_payoff)
        f_j = np.sum(neb_temp_payoff)

        # Fermi updating
        # fi_lambda = np.sum(nebmat[focal_player - 1])  # normlization
        # fj_lambda = np.sum(nebmat[select_neb - 1])
        # w = 1 / (1 + math.exp(((f_i / fi_lambda) - (f_j / fj_lambda)) / 0.1)) # k = .1
        w = 1 / (1 + math.exp((f_i - f_j) / 0.1))  # k = .1
        if np.random.rand(1) <= w: s_set[focal_player - 1] = s_set[select_neb - 1]
        print("\n th {} Rounds of Fermi updating:\n {}".format(f, s_set))

    # find Phi
    for m in range(G.number_of_nodes()): # #(m) matrix
        for c in range(G.number_of_nodes()): # #(col) of #(m)
            Phi[m][l][c] = Use_Tools.payoff(np.array(s_set[m]), M, np.array(s_set[c]))

    print("\n\n th {} Rounds of MC Simulation".format(l))
print("\n\n Y = \n {} \n\n Dim(Y) = {}".format(Y, Y.shape))

# check weather Y and Phi are correct
Y1_hat = np.matmul(np.matrix(Phi[0]),nebmat[:,0])
Y1 = Y[:,0].reshape(L, 1)
Y1 == Y1_hat


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM