基于SIR模型利用标签传播算法的溯源【Python】

Python大作业,题目如下,这里传下我的屎山代码以供参考。

目录

题目

代码

步骤

结果 


题目

代码

import sys
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
import random
from sklearn.metrics import f1_score, roc_auc_score
import warnings
import time
import multiprocessing as mp

np.set_printoptions(threshold=sys.maxsize)  # 显示全部矩阵 调试更方便
warnings.filterwarnings('ignore')  # 忽略futurewarning
plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体 使得画图可以显示中文


def initialize_graph(N, p, num):
    """初始化num个节点为感染节点

    :param N:网络规模
    :param p:连边概率
    :param num:初始感染数量
    :return:(随机ER网络,初始感染节点列表)
    """
    graph = nx.random_graphs.erdos_renyi_graph(N, p)  # 生成包含N个节点、连边概率p的ER随机图
    while not nx.is_connected(graph):  # 重新生成直到是连通图 否则后面求逆会出错
        graph = nx.random_graphs.erdos_renyi_graph(N, p)

    # pos = nx.spring_layout(graph)  # 节点分布形式 这里选取节点随机分布
    # nx.draw_networkx(graph, pos, node_color="green", edge_color="red", with_labels=False, node_size=5)
    # plt.show()
    initial_infected = random.sample(list(graph.nodes()), num)  # 随机选择num个节点作为初始感染节点
    for node in graph.nodes():  # 遍历节点
        if node in initial_infected:  # 如果是初始感染节点 就把节点的status属性设为I 否则设为S
            graph.nodes[node]["status"] = "I"  # 初始感染节点状态为感染
        else:
            graph.nodes[node]["status"] = "S"  # 其他节点初始状态为健康

    return graph, sorted(initial_infected)


def SIR_model(graph, beta, miu, t_obs):
    """SIR模型 先感染再康复

    :param graph:图
    :param beta:感染概率
    :param miu:康复概率
    :param t_obs:观测时刻
    :return:经过t_obs天后的图
    """
    for _ in range(t_obs):
        old_graph = graph.copy()  # 备份
        for node in old_graph.nodes():
            if old_graph.nodes[node]["status"] == "S":  # 如果是健康节点
                neighbors = list(old_graph.neighbors(node))  # 找该健康节点的所有邻接节点
                cnt = sum(1 for neighbor in neighbors if old_graph.nodes[neighbor]["status"] == "I")  # 有cnt个感染节点邻居
                if random.random() < 1 - (1 - beta) ** cnt:  # 该节点被感染的概率为 1−(1−𝛽)^𝑚
                    graph.nodes[node]["status"] = "I"  # 感染
        for node in old_graph.nodes():
            if old_graph.nodes[node]["status"] == "I":  # 如果是感染节点
                if random.random() < miu:
                    graph.nodes[node]["status"] = "R"  # 康复
    return graph


def cal_S(graph):
    """计算相似度矩阵S

    :param graph:图
    :return:
    """
    adj_matrix = nx.adjacency_matrix(graph).toarray()  # graph转邻接矩阵
    row_sums = np.sum(adj_matrix, axis=1)  # 求行和
    D = np.diag(row_sums)  # 转成对角矩阵
    D_new = np.linalg.inv(np.sqrt(D))  # 求-0.5次幂 如果测试规模太小导致生成的图含有孤立结点 这里就会报错 因为含有孤立结点的图的邻接矩阵是个奇异矩阵 不能求逆
    S = np.dot(np.dot(D_new, adj_matrix), D_new)  # D^-0.5 * W * D^-0.5
    return S


def label_rev(graph, S, alpha, max_iterations):
    """标签反向传播

    :param graph:图
    :param S:相似度矩阵
    :param alpha:传播系数
    :param max_iterations:最大迭代次数
    :return:收敛或经过最大迭代次数的标签
    """
    labels = [1 if graph.nodes[node]["status"] == "I" else -1 for node in graph.nodes()]  # 感染状态标签设置为1 易感和康复设为-1
    Y = np.array(labels).astype(float)  # 初始标签向量Y 转成浮点型的array 不然是整型的
    G = Y.copy()  # 一开始为初始标签
    for iter in range(max_iterations):  # 迭代次数
        old_G = G.copy()  # 拷贝上一次迭代的标签 拿来判断收敛的
        for i in range(len(Y)):
            neighbors = list(graph.neighbors(i))  # 找所有邻接节点
            # print("第{}次迭代 节点{}的邻居是{}".format(iter, i, neighbors))
            label_sum = sum([S[i][j] * G[j] for j in neighbors])  # 遍历邻接节点 根据相似度矩阵加权求和
            G[i] = alpha * label_sum + (1 - alpha) * Y[i]  # 更新标签
        if np.allclose(old_G, G, rtol=1e-16, atol=1e-16):  # 收敛 old_G和G非常相似的话就判定收敛 停止迭代 相对容差和绝对误差这里设的很小 差不多是双精度极限了
            # print("old_G={}\nG={}".format(old_G,G))
            print("在第{}次迭代收敛".format(iter))
            break
    else:
        print("到达最大迭代次数")
    return G


def find_source(graph, G):
    """溯源找初始传播者 若节点标签大于所有邻接节点标签和 则认为该节点是初始传播者

    :param graph:图
    :param G:标签
    :return:初始传播者列表
    """
    predict_infected = []
    for node in graph.nodes():
        adj_matrix = nx.adjacency_matrix(graph).toarray()  # graph转邻接矩阵
        if G[node] > max(adj_matrix[node] * G):  # 如果该节点大于所有邻居节点的标签 即认为是初始传播者
            predict_infected.append(node)
    return predict_infected
    # return [node for node in graph.nodes() if G[node] >max(nx.adjacency_matrix(graph).toarray()[node] * G) ]  # 精简版 虽然推导式被优化过 但可读性垃圾


def accuracy(init_infected, predict_infected, N):
    # 调库算准确率
    true_labels = [1 if node in init_infected else 0 for node in range(N)]  # 真实值:初始感染者为1,其他为0
    predicted_labels = [1 if node in predict_infected else 0 for node in range(N)]  # 预测值:预测感染者为1,其他为0
    f1 = f1_score(true_labels, predicted_labels)  # 调库算f1
    auc = roc_auc_score(true_labels, predicted_labels)  # 调库算auc
    return f1, auc


def single_simulate(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected):
    # 单次模拟 返回单个f1和auc的值
    graph, init_infected = initialize_graph(N, p, num_initial_infected)  # 初始化网络
    result_graph = SIR_model(graph, beta, miu, t_obs)  # 在t_bos天观测节点情况
    S = cal_S(result_graph)  # 计算相似度矩阵
    G = label_rev(graph, S, alpha, max_iterations)  # 标签迭代
    # print("最终标签:{}".format(G))
    predict_infected = find_source(graph, G)  # 溯源推测初始传播者
    print("真实初始传播者:{}".format(init_infected))
    print("推测初始传播者:{}".format(predict_infected))
    f1, auc = accuracy(init_infected, predict_infected, N)  # 计算f1和auc
    print("f1-score:{:.2f}".format(f1))
    print("auc:{:.2f}".format(auc))
    return f1, auc


def plot(x, y0, y1, xlabel):
    # 画双Y轴折线图 y0是f1 y1是auc

    fig, ax1 = plt.subplots(figsize=(12.8, 7.2))
    ax2 = ax1.twinx()

    line1 = ax1.plot(x, y0, lw=3, ls='-', c='b', label="f1-score")
    line2 = ax2.plot(x, y1, lw=3, ls='-', c='r', label="auc")  # 设置横坐标标签

    plt.xticks(x)  # 设置横坐标刻度
    plt.title("{}-f1与auc准确率关系图".format(xlabel))  # 设置图表标题
    ax1.set_xlabel(xlabel)  # 设置横坐标标签
    ax1.set_ylabel("f1准确率", color='b')  # 设置y0纵坐标标签
    ax1.tick_params(axis='y', labelcolor='b')
    ax2.set_ylabel("auc准确率", color='r')  # 设置y1纵坐标标签
    ax2.tick_params(axis='y', labelcolor='r')
    lines = line1 + line2
    plt.legend(lines, [l.get_label() for l in lines], loc='best')  # 图例

    fig.tight_layout()

    fig.savefig("{}-f1,auc准确率关系图".format(xlabel))  # 保存


def param_optimize(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, param, paramrange, maxtimes):
    # 改变参数 优化 每个参数都模拟maxtimes次 计算得到的f1和auc取均值
    f1_list = []
    auc_list = []
    if param == "p":
        for p in paramrange:
            f1 = auc = 0  # 该参数的f1和auc总和
            for _ in range(maxtimes):
                single_f1, single_auc = single_simulate(N, p, alpha, beta, miu, t_obs, max_iterations,
                                                        num_initial_infected)
                f1 += single_f1
                auc += single_auc
            f1_list.append(f1 / maxtimes)  # 取均值
            auc_list.append(auc / maxtimes)  # 取均值
        return paramrange, f1_list, auc_list, "连边概率p"
    elif param == "beta":
        for beta in paramrange:
            f1 = auc = 0  # 该参数的f1和auc总和
            for _ in range(maxtimes):
                single_f1, single_auc = single_simulate(N, p, alpha, beta, miu, t_obs, max_iterations,
                                                        num_initial_infected)
                f1 += single_f1
                auc += single_auc
            f1_list.append(f1 / maxtimes)  # 取均值
            auc_list.append(auc / maxtimes)  # 取均值
        return paramrange, f1_list, auc_list, "感染率beta"

    elif param == "alpha":
        for alpha in paramrange:
            f1 = auc = 0  # 该参数的f1和auc总和
            for _ in range(maxtimes):
                single_f1, single_auc = single_simulate(N, p, alpha, beta, miu, t_obs, max_iterations,
                                                        num_initial_infected)
                f1 += single_f1
                auc += single_auc
            f1_list.append(f1 / maxtimes)  # 取均值
            auc_list.append(auc / maxtimes)  # 取均值
        return paramrange, f1_list, auc_list, "传播概率alpha"

    elif param == "t_obs":
        for t_obs in paramrange:
            f1 = auc = 0  # 该参数的f1和auc总和
            for _ in range(maxtimes):
                single_f1, single_auc = single_simulate(N, p, alpha, beta, miu, t_obs, max_iterations,
                                                        num_initial_infected)
                f1 += single_f1
                auc += single_auc
            f1_list.append(f1 / maxtimes)  # 取均值
            auc_list.append(auc / maxtimes)  # 取均值
        return paramrange, f1_list, auc_list, "观测时刻t_obs"


if __name__ == "__main__":
    N = 100  # 规模
    p = 0.08  # 连边概率
    alpha = 0.1  # 传播概率
    beta = 0.15  # 感染概率
    miu = 0.1  # 康复概率
    t_obs = 4  # 观测时刻
    max_iterations = 100  # 标签迭代次数
    num_initial_infected = 5  # 初始感染人数

    # print(single_simulate(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected))

    # 取10次均值
    # start_t = time.perf_counter()  # 计时
    # plot(*param_optimize(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, param="p",
    #                paramrange=np.arange(0.06, 0.32, 0.02), maxtimes=10))
    # plot(*param_optimize(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, param="beta",
    #                paramrange=np.arange(0.05, 1.05, 0.05), maxtimes=10))
    # plot(*param_optimize(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, param="alpha",
    #                paramrange=np.arange(0.1, 1.1, 0.1), maxtimes=10))
    # plot(*param_optimize(N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, param="t_obs",
    #                paramrange=np.arange(2, 9, 1), maxtimes=10))
    # end_t = time.perf_counter()
    # print("消耗: " + "{:.2f}".format(end_t-start_t) + " 秒")


    # 下面是多进程计算 自己的电脑用上面的单核计算72秒 下面并行计算36秒
    start_t = time.perf_counter()  # 计时
    num_cores = int(mp.cpu_count())
    print("本地计算机有: " + str(num_cores) + " 核心")
    if num_cores <= 4:
        pool = mp.Pool(num_cores)
    else:
        pool = mp.Pool(4)  # 最多用四个核心
    param_dict = {
        'task1': (N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, "p",
            np.arange(0.06, 0.32, 0.02),10),
        'task2': (N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, "beta",
            np.arange(0.05, 1.05, 0.05), 10),
        'task3': (N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, "alpha",
            np.arange(0.1, 1.1, 0.1),10),
        'task4': (N, p, alpha, beta, miu, t_obs, max_iterations, num_initial_infected, "t_obs",
            np.arange(2, 9, 1),10)}
    results = [pool.apply_async(param_optimize, args=args) for args in param_dict.values()]
    results = [p.get() for p in results]
    # print(results)
    for result in results:
        plot(*result)  # 画图
    end_t = time.perf_counter()
    print("多进程计算 消耗: " + "{:.2f}".format(end_t - start_t) + " 秒")

步骤

 (偷懒直接复制报告里的内容了)

1. 生成ER随机网络:通过initialize_graph函数,生成了一个包含N个节点、连边概率为p的ER随机图,一开始用的列表来做整体的存储结构,取上半矩阵转置求和,后来发现有个networkx库,直接调用nx.random_graphs.erdos_renyi_graph(N, p)来生成ER随机网络了,值得注意的是这里要重复生成ER图直到是连通图,不然后面求相似度矩阵中求逆会报差错(奇异矩阵不能求逆)。初始时,随机选择了num个节点作为感染节点(后来取的是五个初始感染节点)。并把该节点的status属性改为I,代表感染节点,其余节点status属性改为S,代表健康节点。返回ER图和初始感染者列表。

2. SIR传播模型:利用SIR_model函数,实现了离散的SIR传播动力学模型。该模型在t_obs天观测节点状态,其中感染概率为beta,康复概率为miu。值得注意的是这里是先感染再康复。遍历t_obs,每次遍历时先拷贝原来的ER图(之前没备份导致出来的预测率很低,找了一万年bug)。模拟感染过程,遍历节点,如果是健康节点,计算邻居节点的感染数量,若随机数小于1(1𝛽)^𝑚则判定感染,status改为I。模拟康复过程,遍历节点,如果是感染节点,若随机数小于μ则判定康复,status改为R。值得注意的是,这里的康复节点假设已经有抗体,不会再被感染。

3. 相似度矩阵计算: cal_S函数,通过nx.adjacency_matrix将ER图转为邻接矩阵,计算每行的和,再将行和转对角矩阵,再求-0.5次幂(图含孤立结点就会报错)得到D^-0.5,最后返回D^-0.5 * W * D^-0.5,即为相似度矩阵S。

4. 标签反向传播:label_rev函数,进行标签反向传播迭代,推测感染源。Y是初始标签向量(这里采取了float型,精度越高越好),G一开始等于初始标签向量,在每次迭代中,先拷贝G为old_G,再遍历所有节点,计算该节点的所有邻居节点与相似度矩阵的乘积的和,label_sum = sum([S[i][j] * G[j] for j in neighbors])即公式

再更新标签G,G[i] = alpha * label_sum + (1 - alpha) * Y[i]。每次迭代后都要利用np.allclose(old_G, G, rtol=1e-16, atol=1e-16)判定old_G和G是不是相似,若绝对容差小于1e-16或者相对容差小于1e-16则判定收敛跳出大循环(这里给的容差很小,不然迭代个几次就收敛了)。或者到达最大迭代次数也停止。返回标签向量

5. 溯源找初始传播者:find_source函数,找到初始传播者。遍历图中的节点,如果节点的标签值比所有邻接节点的标签值都大,则认为该节点是初始传播者,将该节点加入到预测感染者列表中并返回。

6. 准确率计算:accuracy函数,这里是调用库一步到位的,f1_score和roc_auc_score函数计算了预测的感染节点与真实感染节点的准确率。

7. 单次模拟:single_simulate函数,上述步骤封装成单次模拟,返回一次模拟的f1和auc的值。在成功单次模拟的基础上,接下来考虑对不同的参数进行优化。

8. 参数优化实验:param_optimize函数,对不同参数(连边概率p、感染概率beta、传播概率alpha、观测时刻t_obs)进行优化实验,控制变量法改变单个参数的值,在每个参数范围内进行maxtimes次模拟(这里取10次模拟的均值作为该参数的f1和auc值,尽量规避实验偶然性),并通过plot函数绘制相关的准确率图表。

连边概率p在范围[0.06, 0.32]内以步长0.02进行计算,每次实验取10次均值。

感染概率beta在范围[0.05, 1.05]内以步长0.05进行计算,每次实验取10次均值。

传播概率alpha在范围[0.1, 1.1]内以步长0.1进行计算,每次实验取10次均值。

观测时刻t_obs在范围[2, 9]内以步长1进行计算,每次实验取10次均值。

9. 主程序:在`__main__`中,设置了实验所需的参数,并依次进行了四次参数优化实验。

10. 由于大部分计算为数值计算,考虑使用并行计算进行优化,这里使用multiprocessing库,获取计算机的CPU数量,最多使用四核心来进行计算,放四个计算任务丢进进程池里,整体速度快了很多。

结果 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值