matlab では,配列の要素のうち特定の条件を満たすものを参照する際,ものすごく効率的な方法が用意されている.それが
Logical Subscripting だ(
logical indexing という人もいる).基本的な説明は Matlab の help の Getting Started や
mathworks のページにも載っているが,もう少し掘り下げてみよう.
ここでいう Logical とは,論理値配列の意味の論理である.早速やってみよう.
例: 行列 A のうち,値が 0.5 未満の要素は 0 にする.閾値処理ですな.
>> A = rand(3)
A =
0.8147 0.9134 0.2785
0.9058 0.6324 0.5469
0.1270 0.0975 0.9575
>> A(A<0.5) = 0
A =
0.8147 0.9134 0
0.9058 0.6324 0.5469
0 0 0.9575
その通り,find は使わなくてよいのである.今まで使いまくってたよ‥
A(find(A<0.5)) = 0
とか書いてたよ‥.はずかちー.
[多次元インデクス自体については
matlab: 一目で分かる多次元インデクス をどうぞ]
もちろん,A の参照に A に関する論理式を使う必要はない.他のものでも使える.
例:同サイズの別配列による条件づけ
>> Score = [60,55,80]
Score =
60 55 80
>> Effort = [50,75,60]
Effort =
50 75 60
>> Score(Effort>70) = Score(Effort>70) + 20
Score =
60 75 80
頑張った人に+20点とかね.Score(Effort>70)が2回出てくるのがなんとも野暮ったいけど,何とかならないだろうか?以下,速度と効率を追求してみよう.
* 速度の検討
logical は比較的小さい容量になる(1byte/element)ので,保存して使いまわしてもよさそうだ.
例: 大きな配列の論理参照における論理配列の使いまわしの効果
>> S=rand(5000);S1=S;N=10;T=zeros(N,1);
>> for n=1:N,tic;S1(S>0.5)=S(S>0.5)+2;T(n)=toc;end;mean(T)
ans =
1.7190
>> S=rand(5000);S1=S;N=10;T=zeros(N,1);
>> for n=1:N,tic;LS=S>0.5;S1(LS)=S(LS)+2;T(n)=toc;end;mean(T)
ans =
1.3880
2割以上速くなる.ちなみに,自分の過去のやり方(findする)だと
>> S=rand(5000);S1=S;N=10;T=zeros(N,1);
>> for n=1:N,tic;IS=find(S>0.5);S1(IS)=S(IS)+2;T(n)=toc;end;mean(T)
ans =
1.6312
あれ?意外と速い.個数の問題か?
例: 参照個数による処理時間の変化(find vs. logical subscripting)
>> S=rand(5000);S1=S;N=10;T=zeros(N,1);
>> for n=1:N,tic;IS=find(S>0.9);S1(IS)=S(IS)+2;T(n)=toc;end;mean(T)
ans =
0.6060
うおっ.てきめん.一方の logical subscripting はどうか?
>> S=rand(5000);S1=S;N=10;T=zeros(N,1);
>> for n=1:N,tic;LS=S>0.9;S1(LS)=S(LS)+2;T(n)=toc;end;mean(T)
ans =
0.6400
>> S=rand(5000);S1=S;N=10;T=zeros(N,1);
>> for n=1:N,tic;LS=S>0.1;S1(LS)=S(LS)+2;T(n)=toc;end;mean(T)
ans =
1.1668
な ん で だ よ
なんで速度に差が出るのよ.たとえば,裏で find や sparse 的なことをしてて,対象個数が少ないほうに合わせてどうこうしてるのか?
条件に引っかかる個数を減らしながら,かかる時間をプロットしてみよう.
例: 参照個数による処理時間の変化(Logical Subscripting)
>> S=rand(5000);S1=S;N=4;LN=15;LVLs=linspace(0,1,LN);T=zeros(N,LN);
>> for ln=1:LN,for n=1:N,tic;LS=S>LVLs(ln);S1(LS)=S(LS)+2;T(n,ln)=toc;end;end;
>> figure;plot(LVLs,mean(T,1),'o-','linewidth',3);set(gca,'fontsize',20);grid on;
>> ylabel('elapsed time');xlabel('the threshold used');
>> print(gcf,'-dpng','logical_subscripting_etimes');
そうしてできたプロットがこれである.
参照される要素数が,
多い(グラフ左):半分より多くなるほど線形に速くなるが,係数は小さい.
半分(グラフ中央):一番遅い
少ない(グラフ右):半分より少なくなるほど,おおむね比例的に速くなる.
という結果が得られた.
例: 参照個数による処理時間の変化(find)
>> S=rand(5000);S1=S;N=4;LN=15;LVLs=linspace(0,1,LN);T=zeros(N,LN);
>> for ln=1:LN,for n=1:N,tic;IS=find(S>LVLs(ln));S1(IS)=S(IS)+2;T(n,ln)=toc;end;end;
>> figure;plot(LVLs,mean(T,1),'o-','linewidth',3);set(gca,'fontsize',20);grid on;
>> ylabel('elapsed time');xlabel('the threshold used');
>> print(gcf,'-dpng','find_etimes');
find でやると素直に
要素が少なくなると処理が早く済む,となる.
二つの結果を重ねると
基本的には logical subscripting を使っていればよさそうだ.要素が少なくなってくると find がわずかに早いこともあるが,その差は微小だ.
logical subscripting の挙動の理由はいまいちわかりませんが,パフォーマンスにこだわる方は参考にしてみてください.ちなみに,R2007b on Win7 x64 on core2duo (mobile) です.
*プログラミング上の性質の検討
この Logical Subscripting の要点は,まさに logical 型の配列を括弧内に与えることにある.カッコ内に与えられた配列が logical かどうかはちゃんとチェックされ,数値 0 と論理値 false は区別される.
例:0 と false の区別
>> a=1
a =
1
>> a(0)=3
??? Subscript indices must either be real positive
integers or logicals.
>> a(logical(0))=3
a =
1
>> a(logical(1))=3
a =
3
また,少なくとも R2007b では,参照に使う論理配列は,元の配列より小さくてもよいようだ.
例:参照に使う論理配列と入れ物の配列の大小
>> B=reshape([1:6],[3,2])
B =
1 4
2 5
3 6
>> B(logical([0,0,0,1]))=9
B =
1 9
2 5
3 6
逆に,大きいと怒られる.
>> B(logical([0,0,0,0,0,0,1]))=9
??? In an assignment A(I) = B, a matrix A cannot
be resized.
ご参考まで.
-- 当ブログの matlab 関係へは
こちらからどうぞ.--