我在numpy中有三个名为RowIndex
,ColIndex
和Entry
的数组。实质上,这是来自矩阵的条目子集,分别具有行索引,列索引和这三个变量中该条目的值。我有两个numpy二维数组(矩阵)U
和M
。让alpha
和beta
为两个给定的常量。我需要遍历矩阵的条目子集,如果我遍历RowIndex
,ColIndex
和Value
,这是可能的。说,
i=RowIndex[0], j=ColIndex[0], value = Entry[0]
然后我需要根据某个等式分别更新i
和j
的{{1}}和U
列。然后,我做
M
等等。详情如下。
i=RowIndex[1], j=ColIndex[1], value = Entry[1]
问题是代码非常慢。是否有任何代码部分可以加快速度?
PS:对于好奇的人来说,这是着名的NetFlix百万奖金问题的获奖解决方案的变种。 RowIndex对应于用户,ColIndex对应于与其评级对应的电影和值。大多数评级都缺失了。已知的评级在RowIndex,ColIndex和Entry中叠加。现在,您尝试查找矩阵U和M,这样for iter in np.arange(length(RowIndex)):
i = RowIndex[iter]
j = ColIndex[iter]
value = Entry[iter]
e = value - np.dot(U[i,:],M[:,j])
OldUi = U[i,:]
OldMj = M[:,j]
U[i,:] = OldUi + beta * (e*OldMj - alpha*OldUi)
M[:,j] = OldMj + beta * (e*OldUi - alpha*OldMj)
'用户对i
'电影的评分由j
给出。现在,根据可用的评级,您尝试使用上面代码中所示的更新公式找到矩阵U和M(或它们的行和列)。
答案 0 :(得分:4)
我认为如果我没有理解错误,那么您的代码可以按照以下方式进行矢量化:
import numpy as np
U, M = # two 2D matrices
rows_idx = # list of indexes
cols_idx = # list of indexes
values = # np.array() of values
e = values - np.dot(U[rows_idx, :], M[:, cols_idx]).diagonal()
Uo = U.copy()
Mo = M.copy()
U[rows_idx, :] += beta * ((e * Mo[:, cols_idx]).T - alpha * Uo[rows_idx, :])
M[:, cols_idx] += beta * ((e * Uo[rows_idx, :].T) - alpha * Mo[:, cols_idx])
下面,
e = values - np.dot(U[rows_idx, :], M[:, cols_idx]).diagonal()
计算你的
e = value - np.dot(U[i,:],M[:,j])
请注意,您想要的结果位于矩阵之间的点积的对角线上。
这不会处理顺序更新(因为没有可用的矢量化),但它将允许您以矢量化和更快的方式执行一批独立更新。
如上所述,我向你建议的代码不能处理顺序更新,因为根据定义,顺序更新方案无法进行矢量化。任何形式
A(t) = A(t-1) +/* something
t
定义时间,不能并行更新。
所以,我提出的是独立更新的矢量化更新。
想象一下,每个M
和U
都有10x10
行,并且您拥有以下行和列索引:
rows_idx = [1, 1, 3, 4, 5, 0]
cols_idx = [7, 1, 7, 5, 6, 5]
您可以从中识别出两个独立的集合(考虑到索引是有序的):
rows_idx = [1, 4, 5], [1, 3, 0]
cols_idx = [7, 5, 6], [1, 7, 5]
请注意,独立集由行和列中唯一的索引构成。根据该定义,您可以将所需的循环数从6(在本例中)减少到2:
for i in len(rows_idx):
ridx = rows_idx[i]
cidx = cols_idx[i]
# Use the vectorized scheme proposed above the edit
e = values - np.dot(U[ridx, :], M[:, cidx]).diagonal()
Uo = U.copy()
Mo = M.copy()
U[ridx, :] += beta * ((e * Mo[:, cidx]).T - alpha * Uo[ridx, :])
M[:, cidx] += beta * ((e * Uo[ridx, :].T) - alpha * Mo[:, cidx])
因此,无论您是手动(或轻松)提取独立更新,还是使用搜索算法计算列表,上述代码都会对独立更新进行矢量化。 / p>
为了澄清以下情况,在上面的例子中:
rows_idx = [1, 1, 3, 4, 5, 0]
cols_idx = [7, 1, 7, 5, 6, 5]
第二行无法并行化,因为1
之前已出现过,并且第三列和最后一列无法并行化,原因相同(7
和{{1 }})。因此,行和列都必须是唯一的,我们最终得到2组元组:
5
从这里开始,将取决于您的数据。找到独立集合的问题可能非常昂贵,特别是如果它们中的大多数都依赖于之前的某些更新。
如果您从数据中获得了一种方法(比如说您按时记录了数据)来提取独立集,那么批量更新将对您有所帮助。另一方面,如果您将所有数据放在一起(这很常见),则取决于一个因素:
如果你可以确保独立集rows_idx = [1, 4, 5], [1, 3, 0]
cols_idx = [7, 5, 6], [1, 7, 5]
的长度远远大于独立集N
的数量(这或多或少意味着,如果你最终会有一些M
行/列索引的M = {2,3,4}
个独立集合,那么可能值得寻找独立集合。
换句话说,如果您要以10000种不同的组合更新30位作者和30部电影,那么您的数据可能会依赖于以前的更新,但是,如果您要更新100000位作者和100000部电影30种组合,那么您的数据可能是独立的。
某些伪代码可以找到独立的集合,如果你没有办法在没有信息的情况下提取它们,那就是这样的:
N = 100000, with N >> M
如您所见,为了找到独立的集合,您需要迭代整个行/列索引列表。上面的伪代码不是最有效的伪代码,我非常确定会有特定的算法。但是,如果您的更新可能依赖于以前的更新,那么查找独立集的成本可能高于执行所有顺序更新。
完成:在整篇文章之后,它完全取决于您的数据。
如果您可以事先获得要更新的行/列的方式提取独立集,那么您可以轻松地更新它们的矢量化。
如果您能确保 大多数您的更新将是独立的(例如,independent_sets = [] # list with sets
for row, col in zip(rows_idx, cols_idx):
for iset in independent_sets:
if row and col DONT exist in iset:
insert row and col
break
if nothing inserted:
add new set to independent set
add current (row, col) to the new set
中的990
将是独立的,可能值得尝试找到10000
集。一种近似集合的方法是使用990
:
np.unique
现在# Just get the index of the unique rows and columns
_, idx_rows = np.unique(rows_idx, return_index=True)
_, idx_cols = np.unique(cols_idx, return_index=True)
# Get the index where both rows and columns are unique
idx = np.intersection1d(idx_rows, idx_cols)
包含idx
的rows_idx和cols_idx的位置,希望这可以大大降低您的计算成本。您可以使用我的批量更新来快速更新与这些索引对应的行和列。然后,您可以使用初始方法更新希望几个重复迭代非唯一索引的条目。
如果您有相同演员或电影的多个更新,那么......保留您的顺序更新方案,因为查找独立集将比迭代更新更难。