【问题标题】:How to vectorize searching function in Matlab?如何在 Matlab 中矢量化搜索功能?
【发布时间】:2015-04-07 14:48:27
【问题描述】:

这是一个 Matlab 编码问题(与 intersect not setdiff here 略有不同的版本:

一个有3列的评分矩阵A,第1列是可能重复的用户ID,第2列是可能重复的项目ID,第3列是用户对项目的评分,范围从1到5。

现在,我有一个用户 ID 子集 smallUserIDList 和一个项目 ID 子集 smallItemIDList,然后我想在 smallUserIDList 中找到 A 中用户评分的行,并收集用户评分的项目,并进行一些计算,例如 setdiff with smallItemIDList 并计算结果,如下代码所示:

userStat = zeros(length(smallUserIDList), 1);
for i = 1:length(smallUserIDList)
    A2= A(A(:,1) == smallUserIDList(i), :);
    itemIDList_each = unique(A2(:,2));

    setDiff = setdiff(itemIDList_each , smallItemIDList);
    userStat(i) = length(setDiff);
end
userStat

最后,我发现配置文件查看器显示上面的循环效率低下,问题是如何通过矢量化而不是 for 循环的帮助来改进这段代码?

例如:

输入:

A = [
1 11 1
2 22 2
2 66 4
4 44 5
6 66 5
7 11 5
7 77 5
8 11 2
8 22 3
8 44 3
8 66 4
8 77 5    
]

smallUserIDList = [1 2 7 8]
smallItemIDList = [11 22 33 55 77]

输出:

userStat =

 0
 1
 0
 2

【问题讨论】:

  • 如果您添加示例数据和预期输出,这样人们就可以比较他们的答案了。
  • 我想知道如果您将计算放在函数中的循环内是否会有所帮助 - 这样优化例程就会识别出您只关心 userStat 而不会将其他变量复制到工作区.
  • 是否可能会有两个条目具有相同的 userID 和相同的 itemID 但评分不同?如果没有,只需构建一个稀疏矩阵。
  • @kkuilla 嗨!好主意,我添加了示例数据和输出以使问题更加明确。

标签: matlab optimization matrix vectorization


【解决方案1】:

原版 MATLAB:

据我所知,您的代码相当于:

%// Create matrix such that: user_item_rating(user,item)==rating
user_item_rating = sparse(A(:,1),A(:,2),A(:,3));

%// Keep all BUT the items in smallItemIDList
user_item_rating(:,smallItemIDList) = [];

%// Keep only those users in `smallUserIDList` and use order of this list
user_item_rating = user_item_rating(smallUserIDList,:);

%// Count the number of ratings
userStat = sum(user_item_rating~=0, 2);

如果每个(user,item)-combination 最多有一个评级,这将起作用。而且效率应该很高。

干净的方法,无需重新发明轮子:

从统计工具箱中查看grpstats! 实现可能类似于:

%// Create ratings table
ratings = array2table(A, 'VariableNames', {'user','item','rating'});

%// Remove items we don't care about (smallItemIDList)
ratings = ratings(~ismember(ratings.item, smallItemIDList),:);

%// Keep only users we care about (smallUserIDList) 
ratings = ratings(ismember(ratings.user, smallUserIDList),:);

%// Compute the statistics grouped by 'user'. 
userStat = grpstats(ratings, 'user');

【讨论】:

  • 似乎工作并且确实相当快!请注意 OP,使用 full 包裹 userStat 以将数值数组作为稀疏方法的输出。
  • @knedlsepp 你好!抱歉耽误了回复!!起初我尝试了你的第一个代码,但并不是因为我粗心的变量名错误!现在,我找到了这个错误,我要感谢你的回答,因为它真的进一步提高了我的跑步速度,又减少了 20 秒!真的很震撼!!你想看看我的另外两个问题吗,第一个是在问题链接中添加的,第二个是如何修改你的 if "setIntersect = intersect(itemIDList_each, smallItemIDList); userStat(i) = length(setIntersect);"更改为“userStat(i) = length(itemIDList_each);”。再次感谢您!
【解决方案2】:

这可能是一种vectorized 方法-

%// Take care of equality between first column of A and smallUserIDList to 
%// find the matching row and column indices.
%// NOTE: This corresponds to "A(:,1) == smallUserIDList(i)" from OP.
[R,C] = find(bsxfun(@eq,A(:,1),smallUserIDList.')); %//'

%// Take care of non-equality between second column of A and smallItemIDList. 
%// NOTE: This corresponds to SETDIFF in the original loopy code from OP.
mask1 = ~ismember(A(R,2),smallItemIDList);

AR2 = A(R,2); %// Elements from 2nd col of A that has matches from first step

%// Get only those elements from C and AR2 that has ONES in mask1
C1 = C(mask1);
AR2 = AR2(mask1);

%// Initialized output array
userStat = zeros(numel(smallUserIDList),1);

if ~isempty(C1)%//There is at least one element in C, so do further processing
    
    %// Find the count of duplicate elements for each ID in C1 indexed into AR2.
    %// NOTE: This corresponds to "unique(A2(:,2))" from OP.
    dup_counts = accumarray(C1,AR2,[],@(x) numel(x)-numel(unique(x)));
    
    %// Get the count of matches for each ID in C in the mask1.
    %// NOTE: This corresponds to:
    %//       "length(setdiff(itemIDList_each , smallItemIDList))" from OP.
    accums = accumarray(C,mask1);
    
    %// Store the counts in output array and also subtract the dup counts
    userStat(1:numel(accums)) = accums;
    userStat(1:numel(dup_counts)) = userStat(1:numel(dup_counts)) - dup_counts;
end

基准测试

接下来列出的代码将建议方法的运行时与原始循环代码进行比较 -

%// Size parameters and random inputs with them
A_nrows    = 5000;
IDlist_len = 5000;
max_userID = 1000;
max_itemID = 1000;
A = [randi(max_userID,A_nrows,1) randi(max_itemID,A_nrows,1) randi(5,A_nrows,2)];
smallUserIDList = randi(max_userID,IDlist_len,1);
smallItemIDList = randi(max_itemID,IDlist_len,1);

disp('---------------------------- With Original Approach')
tic
%//   Original posted code
toc

disp('---------------------------- With Proposed Approach'))
tic
%//   Proposed approach code
toc

因此使用三组数据大小获得的运行时间是 -

案例#1:

A_nrows    = 500;
IDlist_len = 500;
max_userID = 100;
max_itemID = 100;
---------------------------- With Original Approach
Elapsed time is 0.136630 seconds.
---------------------------- With Proposed Approach
Elapsed time is 0.004163 seconds.

案例#2:

A_nrows    = 5000;
IDlist_len = 5000;
max_userID = 100;
max_itemID = 100;
---------------------------- With Original Approach
Elapsed time is 1.579468 seconds.
---------------------------- With Proposed Approach
Elapsed time is 0.050498 seconds.

案例#3:

A_nrows    = 5000;
IDlist_len = 5000;
max_userID = 1000;
max_itemID = 1000;
---------------------------- With Original Approach
Elapsed time is 1.252294 seconds.
---------------------------- With Proposed Approach
Elapsed time is 0.044198 seconds.

结论:因此,所提出的方法对原始循环代码的加速似乎是巨大的!

【讨论】:

  • 嗨!我不得不说你的代码太快了!!我用它,数据处理时间至少减少了20秒!!逆天!!事实上,setdiff 只是我代码的一个分支,我还有另一个函数是相交而不是 setdiff,如果 setdiff 被 intersect 替换,你会帮忙吗?非常感谢!!
  • @archenoo 这真的是一个很棒的加速!好吧,不确定这与发布的问题中的代码有何不同。把它作为一个新问题发布怎么样?
  • 我发布了另一个与此相关的问题并希望得到答案,谢谢! stackoverflow.com/questions/29506283/…
【解决方案3】:

我认为您正在尝试为一部分用户删除一组固定的评分并计算剩余评分的数量:

做以下工作:

Asub = A(ismember(A(:,1), smallUserIDList),1:2);
Bremove = allcomb(smallUserIDList, smallItemIDList);
Akeep = setdiff(Asub, Bremove, 'rows');
T = varfun(@sum, array2table(Akeep), 'InputVariables', 'Akeep2', 'GroupingVariables', 'Akeep1');
% userStat = T.GroupCount;

你需要来自 matlab 中心的文件交换的 allcomb 函数,它给出了两个向量的笛卡尔积,而且很容易实现。

【讨论】:

  • 我喜欢使用表格,但是生成所有(smallUserIDList, smallItemIDList) 有点过头了。
  • 顺便说一句:即使在更正了多余的括号和缺少的逗号之后,代码也不起作用,因为在 setdiff 行中,矩阵的列数不相等。
  • @alexandre iolov,嗨!谢谢你的回答!!我尝试了您的代码并修改了“Asub = A(ismember(A(:,1), smallUserIDList), :);”到“Asub = A(ismember(A(:,1), smallUserIDList), 1:2);”,“Var2”到“Akeep2”,“Var1”到“Akeep1”,然后它就可以工作了!!如果使用我的示例数据,结果是 userStat = 1 2,这与我期望的输出有点不同。不过,我可以从你的代码中学到很多新方法,非常感谢!!
  • @knedlsepp - 感谢您的更正。两个矩阵都应该有两列,但我当然没有尝试运行代码——因为我没有 A、smallUserIDList、smallItemIDList 的示例,也没有费心去发明合理的。你会如何避免笛卡尔积?
  • @alexandreiolov:看看我的回答涉及表格。 ismember 步骤也可以应用于您的答案。我认为最终的ratings 变量应该与您的array2table(Akeep) 匹配。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多