优化社交网络演化模型

3 投票
4 回答
578 浏览
提问于 2025-04-16 21:43

我正在写一段代码,用来模拟社交网络的发展。简单来说,每个人都对应一个节点,而人与人之间的关系(在网络中称为边)会根据关系是友好还是不友好,分别赋予+1或-1的权重。

通过这个简单的模型,我们可以判断三个人的关系组合是“平衡”的还是“不平衡”的,这取决于这三个人之间的关系边的乘积是正数还是负数。

最后,我想实现一种类似于伊辛模型的东西。也就是说,随机改变一些关系,如果新的网络中有更多的平衡三人组合(能量更低),那么就保留这个新关系;如果没有,那么这个新关系只有在一定概率下才会被保留。

好了,接下来是我的问题:我写了以下代码,但我的数据集大约有12万个三人组合,因此运行需要4天!

有没有人能给我一些优化代码的建议?

谢谢。

#Importing required librarys

try:
    import matplotlib.pyplot as plt
except:
    raise

import networkx as nx
import csv
import random
import math

def prod(iterable):
    p= 1
    for n in iterable:
        p *= n
    return p


def Sum(iterable):
    p= 0
    for n in iterable:
        p += n[3]
    return p


def CalcTriads(n):  
    firstgen=G.neighbors(n)
    Edges=[]
    Triads=[]

    for i in firstgen:
        Edges.append(G.edges(i))

    for i in xrange(len(Edges)):
        for j in range(len(Edges[i])):# For node n go through the list of edges (j) for the neighboring nodes (i) 
            if set([Edges[i][j][1]]).issubset(firstgen):# If the second node on the edge is also a neighbor of n (its in firstgen) then keep the edge.
                t=[n,Edges[i][j][0],Edges[i][j][1]]
                t.sort()
                Triads.append(t)# Add found nodes to Triads.

    new_Triads = []# Delete duplicate triads.
    for elem in Triads:
        if elem not in new_Triads:
            new_Triads.append(elem)
    Triads = new_Triads 

    for i in xrange(len(Triads)):# Go through list of all Triads finding the weights of their edges using G[node1][node2]. Multiply the three weights and append value to each triad.
            a=G[Triads[i][0]][Triads[i][1]].values()
            b=G[Triads[i][1]][Triads[i][2]].values()
            c=G[Triads[i][2]][Triads[i][0]].values()
            Q=prod(a+b+c)
            Triads[i].append(Q)

    return Triads



###### Import sorted edge data ######       
li=[]
with open('Sorted Data.csv', 'rU') as f:
    reader = csv.reader(f)
    for row in reader:
        li.append([float(row[0]),float(row[1]),float(row[2])])
G=nx.Graph()
G.add_weighted_edges_from(li)


for i in xrange(800000):
    e = random.choice(li)   # Choose random edge

    TriNei=[]
    a=CalcTriads(e[0]) # Find triads of first node in the chosen edge 
    for i in xrange(0,len(a)):
        if set([e[1]]).issubset(a[i]): # Keep triads which contain the whole edge (i.e. both nodes on the edge)
            TriNei.append(a[i])         
    preH=-Sum(TriNei) # Save the "energy" of all the triads of which the edge is a member


    e[2]=-1*e[2]# Flip the weight of the random edge and create a new graph with the flipped edge   
    G.clear()
    G.add_weighted_edges_from(li)


    TriNei=[]
    a=CalcTriads(e[0])  
    for i in xrange(0,len(a)):
        if set([e[1]]).issubset(a[i]):
            TriNei.append(a[i])
    postH=-Sum(TriNei)# Calculate the post flip "energy".   

    if postH<preH:# If the post flip energy is lower then the pre flip energy keep the change
        continue

    elif random.random() < 0.92: # If the post flip energy is higher then only keep the change with some small probability. (0.92 is an approximate placeholder for exp(-DeltaH)/exp(1) at the moment)
        e[2]=-1*e[2]

4 个回答

2

这里有很多地方可以改进。首先,可以使用一个叫做 cProfile 的工具来分析你的程序。这个工具可以告诉你程序大部分时间花在哪里,这样你就能知道哪些地方最需要优化。顺便提一下,你不需要在每次程序运行时都生成所有的三元组。

另外,在你期待得到一个好的答案之前,记得先修正你的缩进问题。

无论如何,这个问题可能更适合放在 Code Review 上。

4

有一些语法上的改动可以让你的代码运行得更快,比如用内置的函数替代你的求和和乘积函数,像这样:sum(x[3] for x in iterable)reduce(operator.mul, iterable)。一般来说,使用内置函数或者生成器表达式比用显式的循环要快。

根据我的理解,这行代码:

    if set([e[1]]).issubset(a[i]): # Keep triads which contain the whole edge (i.e. both nodes on the edge)

是在检查一个浮点数是否在一个浮点数列表里。把它换成 if e[1] in a[i]: 可以避免每次比较时都创建两个 set 对象,从而提高效率。

顺便说一下,如果你只是用索引来访问数组的元素,其实不需要循环遍历索引值。例如,可以把:

for i in range(0,len(a)):
    if set([e[1]]).issubset(a[i]): # Keep triads which contain the whole edge (i.e. both nodes on the edge)
    TriNei.append(a[i])    

换成:

for x in a:
    if set([e[1]]).issubset(x): # Keep triads which contain the whole edge (i.e. both nodes on the edge)
    TriNei.append(x)    

不过我怀疑这些改动对整体运行时间的影响不会很大。要想显著提高速度,你可能需要使用不同的算法,或者换一种更快的编程语言。你可以试试在 pypy 上运行你的代码——在某些情况下,它比 CPython 快很多。你也可以试试 cython,它会把你的代码编译成 C,有时能带来很大的性能提升,特别是当你在代码中添加了 cython 类型信息时。我觉得最大的提升可能来自于换一个工作量更少的算法,但我没有具体的建议。

顺便问一下,为什么要循环 800000 次?这个数字有什么特别的意义吗?

另外,请给你的变量起一些有意义的名字。使用单个字符的名字或者缩写并不会让代码更快,反而会让人很难理解代码在做什么。

5

以下建议可能不会大幅提升你的性能,因为它们并不是针对算法层面的,也就是说,它们并不特别针对你的问题。不过,这些都是一些通用的建议,可以稍微改善性能:

如果你不是在使用Python 3,建议把

for i in range(800000):

改成

for i in xrange(800000):

后者只是简单地从0数到800000,而前者则是先创建一个很大的数字列表,然后再遍历这个列表。对于其他循环,也可以用range来做类似的改动。

另外,把

j=random.choice(range(len(li))) 
e=li[j] # Choose random edge

改成

e = random.choice(li)

并且后面用e代替li[j]。如果你真的需要一个索引号,可以用random.randint(0, len(li)-1)来获取。

撰写回答