【网络博弈】理解与实现基于网络博弈的动力学数据生成


复杂网络与网络博弈

本文省略大量预备知识,并以极简化的方式介绍网络博弈和演化动力学。

常见三种复杂网络

复杂网络本质是一种高连接属性的拓扑结构。在网络上进行博弈过程,网络结构本身对博弈过程会产生影响。

常用的三种网络有: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