【问题标题】:Efficiently constructing sparse biadjacency matrix in Numpy在 Numpy 中高效构建稀疏邻接矩阵
【发布时间】:2015-01-25 11:20:30
【问题描述】:

我正在尝试将此 CSV 文件加载到一个稀疏 numpy 矩阵中,该矩阵将表示此用户到子版块二分图的邻接矩阵:http://figshare.com/articles/reddit_user_posting_behavior/874101

这是一个示例:

603,politics,trees,pics
604,Metal,AskReddit,tattoos,redditguild,WTF,cocktails,pics,funny,gaming,Fitness,mcservers,TeraOnline,GetMotivated,itookapicture,Paleo,trackers,Minecraft,gainit
605,politics,IAmA,AdviceAnimals,movies,smallbusiness,Republican,todayilearned,AskReddit,WTF,IWantOut,pics,funny,DIY,Frugal,relationships,atheism,Jeep,Music,grandrapids,reddit.com,videos,yoga,GetMotivated,bestof,ShitRedditSays,gifs,technology,aww

有 876,961 行(每个用户一条)和 15,122 个子版块以及总共 8,495,597 个用户到子版块的关联。

这是我现在拥有的代码,在我的 MacBook Pro 上运行需要 20 分钟:

import numpy as np
from scipy.sparse import csr_matrix 

row_list = []
entry_count = 0
all_reddits = set()
with open("reddit_user_posting_behavior.csv", 'r') as f:
    for x in f:
        pieces = x.rstrip().split(",")
        user = pieces[0]
        reddits = pieces[1:]
        entry_count += len(reddits)
        for r in reddits: all_reddits.add(r)
        row_list.append(np.array(reddits))

reddits_list = np.array(list(all_reddits))

# 5s to get this far

rows = np.zeros((entry_count,))
cols = np.zeros((entry_count,))
data =  np.ones((entry_count,))
i=0
user_idx = 0
for row in row_list:
    for reddit_idx in np.nonzero(np.in1d(reddits_list,row))[0]:
        cols[i] = user_idx
        rows[i] = reddit_idx
        i+=1
    user_idx+=1
adj = csr_matrix( (data,(rows,cols)), shape=(len(reddits_list), len(row_list)) )

似乎很难相信这是最快的速度......将 82MB 文件加载到列表列表中需要 5 秒,但构建稀疏矩阵需要 200 倍。我能做些什么来加快速度?是否有一些文件格式可以在不到 20 分钟的时间内将此 CSV 转换为可以更快导入的文件格式?有没有我在这里做的一些明显昂贵的操作不好?我尝试构建一个密集矩阵,并尝试创建一个 lil_matrix 和一个 dok_matrix 并一次分配一个 1,但这并没有更快。

【问题讨论】:

  • 双 for 循环的时间是什么时候? csr 调用?我首先尝试将内部 for 循环矢量化,即一次分配多个值。

标签: python performance numpy graph


【解决方案1】:

睡不着,尝试了最后一件事……我终于可以通过这种方式将它缩短到 10 秒:

import numpy as np
from scipy.sparse import csr_matrix 

user_ids = []
subreddit_ids = []
subreddits = {}
i=0
with open("reddit_user_posting_behavior.csv", 'r') as f:
    for line in f:
        for sr in line.rstrip().split(",")[1:]: 
            if sr not in subreddits: 
                subreddits[sr] = len(subreddits)
            user_ids.append(i)
            subreddit_ids.append(subreddits[sr])
        i+=1

adj = csr_matrix( 
    ( np.ones((len(userids),)), (np.array(subreddit_ids),np.array(user_ids)) ), 
    shape=(len(subreddits), i) )

【讨论】:

  • adj的每一行对应的单词是什么?看起来您必须根据值对 subreddits 键进行排序?
【解决方案2】:

首先,您可以将内部的 for 替换为:

reddit_idx = np.nonzero(np.in1d(reddits_list,row))[0]
sl = slice(i,i+len(reddit_idx))
cols[sl] = user_idx
rows[sl] = reddit_idx
i = sl.stop

使用nonzero(in1d()) 查找匹配项看起来不错,但我还没有探索替代方案。通过切片分配的另一种方法是extend 列表,但这可能会更慢,尤其是在有很多行的情况下。

构建行,cols 是迄今为止最慢的部分。对csr_matrix 的调用是次要的。

由于行(用户)比 subreddit 多得多,因此可能值得为每个 subreddit 收集用户 ID 列表。您已经在集合中收集 subreddits。相反,您可以将它们收集在默认字典中,并从中构建矩阵。当在您的 3 行上进行测试时,重复 100000 次它明显更快。

from collections import defaultdict
red_dict = defaultdict(list)
user_idx = 0
with open("reddit_user_posting_behavior.csv", 'r') as f:
    for x in f:
        pieces = x.rstrip().split(",")
        user = pieces[0]
        reddits = pieces[1:]
        for r in reddits:
            red_dict[r] += [user_idx]
        user_idx += 1

print 'done 2nd'
x =  red_dict.values()
adj1 = sparse.lil_matrix((len(x), user_idx), dtype=int)
for i,j in enumerate(x):
    adj1[i,j] = 1

【讨论】:

  • 谢谢!它在我的机器上运行了大约 14 秒,但最终输出看起来不正确。第一行总和应该在 10,000 范围内,但对于此输出,它是 1。如果您查看我的答案,我可以使用一种类似的方法,一次通过和一个字典将其缩短到 10 秒。
  • 我的adj1 与您原来的adj 匹配。你的第二个脚本产生了一个不同的adj。我认为它们的行数相同,但顺序不同 - 字典顺序 v 出现顺序?
猜你喜欢
  • 2013-08-12
  • 2016-06-16
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
  • 2017-07-20
  • 2013-02-06
  • 2023-04-01
  • 1970-01-01
相关资源
最近更新 更多