从稀疏表示创建 Numpy 数组

Create Numpy array from sparse representation

我创建了数据的稀疏表示,并想将其转换为 Numpy 数组。

比方说,我有以下数据(实际上 data 包含更多列表,每个列表更长):

data = [['this','is','my','first','dataset','here'],['but','here', 'is', 'another','one'],['and','yet', 'another', 'one']]

我有两个 dict 项目,将每个单词映射到一个唯一的整数值,反之亦然:

w2i = {'this':0, 'is':1, 'my':2, 'first':3, 'dataset':4, 'here':5, 'but':6, 'another':7, 'one':8, 'and':9, 'yet':10}

此外,我有一个 dict 可以获取每个单词组合的计数:

comb_dict = dict()
for text in data:
    sorted_set_text = sorted(list(set(text)))
    for i in range(len(sorted_set_text)-1):
        for j in range(i+1, len(sorted_set_text)):
            if (sorted_set_text[i],sorted_set_text[j]) in comb_dict:
                comb_dict[(sorted_set_text[i],sorted_set_text[j])] += 1
            else:
                comb_dict[(sorted_set_text[i],sorted_set_text[j])] = 1

根据这个字典,我创建了一个稀疏表示如下:

sparse = [(w2i[k[0]],w2i[k[1]],v) for k,v in comb_dict.items()]

此列表由元组组成,其中第一个值表示 x 轴的位置,第二个值表示 y 轴的位置,第三个值表示同时出现的次数:

[(4, 3, 1),
 (4, 5, 1),
 (4, 1, 1),
 (4, 2, 1),
 (4, 0, 1),
 (3, 5, 1),
 (3, 1, 1),
 (3, 2, 1),
 (3, 0, 1),
 (5, 1, 2),
 (5, 2, 1),
 (5, 0, 1),
 (1, 2, 1),
 (1, 0, 1),
 (2, 0, 1),
 (7, 6, 1),
 (7, 5, 1),
 (7, 1, 1),
 (7, 8, 2),
 (6, 5, 1),
 (6, 1, 1),
 (6, 8, 1),
 (5, 8, 1),
 (1, 8, 1),
 (9, 7, 1),
 (9, 8, 1),
 (9, 10, 1),
 (7, 10, 1),
 (8, 10, 1)]

现在,我想要得到一个 Numpy array (11 x 11),其中每一行 i 和列 j 代表一个词,单元格表示词 i 和 j 同时出现的频率。因此,开始是

cooc = np.zeros((len(w2i),len(w2i)), dtype=np.int16)

然后,我想更新 cooc,以便与 sparse 中的单词组合关联的 row/column 索引将被分配关联值。我该怎么做?

编辑:我知道我可以遍历 cooc 并逐个分配每个单元格。但是,我的数据集很大,这会很耗时。相反,我想将 cooc 转换为 Scipy 稀疏矩阵并使用 toarray() 方法。我该怎么做?

在不使用朴素循环的附加约束后编辑

您可以使用 numba 和 JIT 装饰器在执行前编译循环。这将比一个简单的循环快很多。如果您使用 dtype=np.uint16,您将使用默认 int64 RAM 的 1/4(最大值为 65535,因此您应该可以使用 60k 字)。

我用 3 600 000 000 (num_words2) 组合尝试了这个,在我的 c2016 笔记本电脑上 运行 在 43 秒内完成。

完整代码:

from numba import jit

# Create array of each word with dtype uint16 (max value 65535)
n_unique_words = 11
start_words = np.array([row[0] for row in sparse], dtype = np.uint16)
neighbour_words = np.array([row[1] for row in sparse], dtype = np.uint16)
frequences = np.array([row[2] for row in sparse], dtype = np.uint16)


# Numba loop method
@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def make_cooc_fast(start_words, neighbour_words, frequencies, n_unique_words): # Function is compiled to machine code when called the first time
    
    big_cooc = np.zeros((n_unique_words, n_unique_words), dtype=np.int16)
    
    for i in range(0, len(start_words)):
        big_cooc[start_words[i], neighbour_words[i]] = frequencies[i]
        big_cooc[neighbour_words[i], start_words[i]] = frequencies[i]
    
    return big_cooc

cooc = make_cooc_fast(
    start_words=start_words, 
    neighbour_words=neighbour_words,
    frequencies=frequences,
    n_unique_words=n_unique_words)

原回复

如果我对问题的理解正确,那么您已经完成了所有艰苦的工作。从这里开始,它应该只是:

for row in sparse:
    cooc[row[0], row[1]] = row[2]
    cooc[row[1], row[0]] = row[2]

它是两行,因为如果单词 a 出现在单词 b 旁边 n 次,那么单词 b 也会出现在单词 a 旁边n 次。

这个领域是我感兴趣的领域 - 如果您想进一步讨论,请随时私信我。

In [268]: alist = [(4, 3, 1),
     ...:  (4, 5, 1),
     ...:  (4, 1, 1),
     ...:  (4, 2, 1),
...
     ...:  (9, 10, 1),
     ...:  (7, 10, 1),
     ...:  (8, 10, 1)]

从列表中创建一个数组:

In [269]: arr = np.array(alist)
In [270]: arr.shape
Out[270]: (29, 3)

并使用数组的列来填充定义的 (11,11) 数组中的槽:

In [271]: res = np.zeros((11,11),int)
In [272]: res[arr[:,0],arr[:,1]]=arr[:,2]
In [273]: res
Out[273]: 
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
       [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0],
       [1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0],
       [1, 2, 1, 0, 0, 0, 0, 0, 1, 0, 0],
       [0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0],
       [0, 1, 0, 0, 0, 1, 1, 0, 2, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

您可以使用相同的列创建一个 scipy.sparse 矩阵。

In [274]: from scipy import sparse
In [276]: M = sparse.coo_matrix((arr[:,2],(arr[:,0],arr[:,1])), shape=(11,11))
In [277]: M
Out[277]: 
<11x11 sparse matrix of type '<class 'numpy.int64'>'
    with 29 stored elements in COOrdinate format>
In [278]: M.A
Out[278]: 
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
       [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0],
       [1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0],
       [1, 2, 1, 0, 0, 0, 0, 0, 1, 0, 0],
       [0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0],
       [0, 1, 0, 0, 0, 1, 1, 0, 2, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

我认为这些其他答案有点像重新发明已经存在的轮子。

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer 

data = [['this','is','my','first','dataset','here'],['but','here', 'is', 'another','one'],['and','yet', 'another', 'one']]

我要把它们重新组合起来,然后使用 sklearn 的 CountVectorizer

data = [" ".join(x) for x in data]
encoder = CountVectorizer()
occurrence = encoder.fit_transform(data)

这个出现矩阵是一个稀疏矩阵,把它变成共现矩阵只是一个简单的乘法(对角线是每个token出现的总次数)

co_occurrence = occurrence.T @ occurrence

>>> co_occurrence.A

array([[1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1],
       [1, 2, 1, 0, 0, 1, 1, 0, 2, 0, 1],
       [0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0],
       [0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0],
       [0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0],
       [0, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0],
       [0, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0],
       [0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0],
       [1, 2, 1, 0, 0, 1, 1, 0, 2, 0, 1],
       [0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0],
       [1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1]])

并且可以从编码器中恢复 row/column 个标签:

encoder.vocabulary_

{'this': 9,
 'is': 6,
 'my': 7,
 'first': 4,
 'dataset': 3,
 'here': 5,
 'but': 2,
 'another': 1,
 'one': 8,
 'and': 0,
 'yet': 10}