欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

探索社会科学问题的计算方法:第七章 - 模拟网络中的级联效应(实践中的网络级联行为仿真)

最编程 2024-02-19 16:24:06
...

学习资源来自,一个哲学学生的计算机作业 (karenlyu21.github.io)


1、背景问题

网络级联(network cascades)是传播学的重要理论。每个人是否接受一个新事物,受到周围人的极大影响,这进而影响了新事物在社会中的扩散。

如果我周围有更多人使用新产品A,我也更倾向于放弃原来使用的产品B,转而使用A。这可能是因为

  • 别人选择A,我才有场合接触到和A有关的信息,也更容易对A产生信心。
  • 和朋友们使用同样的产品更方便分享和协作(协调博弈,见下)。

周围人的影响可以用两种模型在社会网络中进行模拟:两个模型都假设一开始有几个初用节点率先使用了A(他们大多是一些*分子),

  • 随机模型:已激活节点以一定概率成功激活其邻居;
  • 门槛模型:未激活节点的已激活邻居达到一定数目,该节点就被激活。

在门槛模型中,门槛值可以用协调博弈理论计算。我们考虑正在使用产品B的节点v,ta在社会网络中有d个邻居。考虑邻居w,节点v和邻居w之间存在协调博弈,其收益矩阵为

image.png

节点v的总回报等于ta与所有邻居在此意义下博弈的综合结果。设ta的邻居使用A的占比为p。当选用A的收益≥选用B的收益时,亦即,当

image.png

时,节点v会选用A,否则会选用B。也就是说,节点v选用A的门槛值q=b/a+b

考虑产品A在社会网络中扩散达成稳定的情况,此时所有节点与周围节点的协调博弈达成均衡。什么条件下,网络中节点将会全部放弃B,逐步转而选择A,实现采用A的完全级联?什么情况下,A在网络中停止了扩散,仅仅达成不完全级联?根据前文的分析,直接的回答是:剩下的每个采用B节点的A邻居数占比都不够大,小于门槛 q=b/a+b

下图是一个形象的示意,采用B的节点集合“抱团很紧”(其中的节点都没有足够的已激活邻居),A攻不进去。除去初用节点,某种“密度”足够大的相关节点集合或“聚簇”,是级联进行不下去的关键。

image.png

我们定义密度为r的聚簇:一个节点集合,其中每个节点至少有占比r的邻居节点也在这集合中。

设网络中一个初用节点集S采用A,删去S后的剩余网络的其他节点采用B,且它们改用A的门槛值为q。基于S能够实现一个完全级联,当且仅当剩余网络中不存在密度大于1−q的聚簇。

网络级联理论对于理解新产品推广很有帮助。课上,我们做了一个小练习:给定一个社会网络,选取哪些节点入手来推广新产品,会有最好的效果。

此外还有一个问题是,网络级联是否一定会终止?级联过程会不会震荡(改用A的人后来又回到B)?答案是,假设初用节点永不变色,后来每一时刻,每个节点的邻居中使用A的人不会变少,级联过程就不会发生震荡,级联一定会终止。

除了新产品的推广以外,网络级联理论还能用来解释行为的扩散。当周围有足够多的人采取一项行动的时候(尤其是比较冒险的行动),一个人才有足够的意愿或勇气这样做。这样,周围的人的行为就扩散到了ta身上。这个人采取行动后,它又会进一步在ta的邻居中间扩散。 这件事的反面是“沉默的螺旋”:由于每个人都不敢率先采取行动或公开意见,尽管很多人都或多或少希望一件事发生,但都保持了沉默。

image.png

在不同类型的网络级联中,弱关系和强关系发挥了不同的作用:

  • 弱关系对于信息传播有很重要的作用,如工作机会、在线视频、各种开放信息;
  • 但对于一个行为的传播,特别是潜在成本较高的行为(例如请总裁辞职等有风险的活动),弱关系作用较小(弱关系往往是“捷径”(local bridge)(见背景问题),容易遇到聚簇的阻碍)。

2、计算实践:网络级联过程的仿真

2.1、作业描述与算法思路

给定一个图的邻接矩阵A初用节点集S门槛值q,模拟网络级联过程,输出从S开始每一步级联得到的新节点,直到停止。

我们的模拟采用门槛模型。在每一步级联中,我们只需要关心:尚未激活的节点的邻居是什么情况,ta的邻居有多少人已被激活,这是否足以激活ta?

2.2、编程实现与要点说明

首先,读取文件,将邻接矩阵存储在numpy 2d-array A中,并要求用户输入门槛值q

# open the file and get the matrix of the network
def arrayGen(filename):
    f = open(filename, 'r')
    r_list = f.readlines()
    f.close()
    array_nl = []
    for line in r_list:
        if line == '\n':
            continue
        line = line.strip('\n')
        line = line.strip()
        row_list = line.split()
        for k in range(len(row_list)):
            row_list[k] = row_list[k].strip()
            row_list[k] = int(row_list[k])
        array_nl.append(row_list)
    n = len(array_nl[0])
    array = np.array(array_nl)
    return array, n

while True:
    filename = input('请输入矩阵名称(net1.txt / net2.txt / net3.txt):')
    try:
        A, n = arrayGen(filename)
    except:
        print('输入错误!', end = '')
        continue
    break

此外,我们还需要让用户输入初用节点集合,存储在列表变量users里:

# input the starting points
while True:
    S_input = input('请输入初用集(0–%i之间的自然数,用半角逗号分开):' % (n-1))
    try:
        S_input = S_input.strip()
        S_list_str = S_input.split(',')
        S_list = []
        for S in S_list_str:
            S = S.strip()
            S = int(S)
            S_list.append(S)
    except:
        print('输入格式不正确。', end = '')
        continue
    break

users = S_list

初始值的一个例子如下:

请输入矩阵名称(net1.txt / net2.txt / net3.txt):net2.txt
请输入初用集(0–16之间的自然数,用半角逗号分开):7,10,12
请输入门槛值(0–1之间的小数):0.3

分析邻接矩阵,把每个节点的朋友有哪些存储在字典变量friends_all里,这方便我们逐节点分析邻居的激活情况。

# write who's whose friend in a dictionary
friends_all = {}
for i in range(n):
    friends_ind = []
    for j in range(n):
        edge = A[i][j]
        if edge == 1:
            friends_ind.append(j)
    friends_all[i] = friends_ind

级联开始。每一步级联中,我们跳过已经在使用新产品的人,逐节点分析剩下的人的邻居。

# cascade
rnd = 0
print('一开始的新用户:', end = '')
print(users)
while True:
    rnd += 1
    new_users = []
    for i in range(n):
        if i in users:
            continue # inspect each individual, see whether he or she wants to switch to the new app

当节点i的邻居中,使用新产品的占比大于门槛值时,亦即len(friends_users) / len(friends_ind) >= q时,ta就会转而使用新产品。

friends_ind = friends_all[i] # all friends
        friends_users = [j for j in friends_ind if j in users] # friends who are using the new app
        influence = len(friends_users) / len(friends_ind) # the portion of friends who are using the new app
        if influence >= q: # willing to switch
            new_users.append(i)
    users += new_users

当已激活节点无法再激活新的节点new_users == [],级联结束,网络达成稳定。

    if new_users == []:
        print('级联结束。')
        break
    print('第%i轮的新用户:' % rnd, end = '')
    print(new_users)

在上述初始值的例子里,级联过程输出如下

请输入矩阵名称(net1.txt / net2.txt / net3.txt):net2.txt
请输入初用集(0–16之间的自然数,用半角逗号分开):7,10,12
请输入门槛值(0–1之间的小数):0.3
一开始的新用户:[7, 10, 12]
第1轮的新用户:[4, 11, 13, 14, 16]
第2轮的新用户:[3, 6, 9, 15]
第3轮的新用户:[5, 8]
第4轮的新用户:[1]
第5轮的新用户:[0, 2]
级联结束。

3、完整代码

网络级联过程

import numpy as np


# simulate the network cascade
# open the file and get the matrix of the network
def arrayGen(filename):
    f = open(filename, 'r')
    r_list = f.readlines()
    f.close()
    array_nl = []
    for line in r_list:
        if line == '\n':
            continue
        line = line.strip('\n')
        line = line.strip()
        row_list = line.split()
        for k in range(len(row_list)):
            row_list[k] = row_list[k].strip()
            row_list[k] = int(row_list[k])
        array_nl.append(row_list)
    n = len(array_nl[0])
    array = np.array(array_nl)
    return array, n

while True:
    filename = input('请输入矩阵名称(net1.txt / net2.txt / net3.txt):')
    try:
        A, n = arrayGen(filename)
    except:
        print('输入错误!', end = '')
        continue
    break

# input the starting points
while True:
    S_input = input('请输入初用集(0–%i之间的自然数,用半角逗号分开):' % (n-1))
    try:
        S_input = S_input.strip()
        S_list_str = S_input.split(',')
        S_list = []
        for S in S_list_str:
            S = S.strip()
            S = int(S)
            S_list.append(S)
    except:
        print('输入格式不正确。', end = '')
        continue
    break

users = S_list

# input the threshold value
while True:
    q = input('请输入门槛值(0–1之间的小数):')
    try:
        q = float(q)
    except:
        print('输入格式不正确。', end = '')
        continue
    break

# write who's whose friend in a dictionary
friends_all = {}
for i in range(n):
    friends_ind = []
    for j in range(n):
        edge = A[i][j]
        if edge == 1:
            friends_ind.append(j)
    friends_all[i] = friends_ind

# cascade
rnd = 0
print('一开始的新用户:', end = '')
print(users)
while True:
    rnd += 1
    new_users = []
    for i in range(n):
        if i in users:
            continue # inspect each individual, see whether he or she wants to switch to the new app
        friends_ind = friends_all[i] # all friends
        friends_users = [j for j in friends_ind if j in users] # friends who are using the new app
        influence = len(friends_users) / len(friends_ind) # the portion of friends who are using the new app
        if influence >= q: # willing to switch
            new_users.append(i)
    users += new_users
    if new_users == []:
        print('级联结束。')
        break
    print('第%i轮的新用户:' % rnd, end = '')
    print(new_users)

net1.txt

0 1 1 1 0 0
1 0 1 0 1 0
1 1 0 1 1 1
1 0 1 0 0 1
0 1 1 0 0 1
0 0 1 1 1 0

net2.txt

0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0
0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0
0 0 0 1 1 0 0 1 1 1 0 0 0 0 0 0 0
0 0 0 0 1 0 1 0 0 1 0 0 0 1 0 0 0
0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0
0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0
0 0 0 0 0 0 0 0 0 1 1 0 1 0 1 1 0
0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1
0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 1
0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0

net3.txt

0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 1 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 
0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 
0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 
0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 
1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 
0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 
0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 
0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 
1 0 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 
0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0