MATLAB代碼矢量化指南

MATLAB索引或引用(MATLAB Indexing or Referencing) 
MATLAB中有三種基本方法可以選取一個矩陣的子陣。它們分別是 下標法,線性法和邏輯法(subscripted, linear, and logical)。 
1.1)下標法 
非常簡單,看幾個例子就好。 
A = 6:12; 
A([3,5]) 
ans = 8 10 
A([3:2:end]) 
ans = 8 10 12 
A = 
[11 14 17; ... 
12 15 18; ... 
13 16 19]; 
A(2:3,2) 
ans = 
15 
16 
1.2)線性法 
二維矩陣以列優先順序可以線性展開,可以通過現行展開後的元素序號來訪問元素。 
A = 
[11 14 17; ... 
12 15 18; ... 
13 16 19]; 
A(6) 
ans = 16 
A([3,1,8]) 
ans = 13 11 18 
A([3;1;8]) 
ans = 
13 
11 
18 

1.3)邏輯法 
用一個和原矩陣具有相同尺寸的0-1矩陣,可以索引元素。在某個位置上爲1表示選取元,否則不選。得到的結果是一個向量。 
A = 6:10; 
A(logical([0 0 1 0 1])) 
ans = 
8 10 
A = 
[1 2 
3 4]; 
B = [1 0 0 1]; 
A(logical(B)) 
ans = 
1 4 
---------------------------我是分隔線^_^-------------------------- 
2)數組操作和矩陣操作(Array Operations vs. Matrix Operations) 
對矩陣的元素一個一個孤立進行的操作稱作數組操作;而把矩陣視爲 
一個整體進行的運算則成爲矩陣操作。MATLAB運算符*,/,\,^都是矩陣 
運算,而相應的數組操作則是.*, ./, .\, .^ 
A=[1 0 ;0 1]; 
B=[0 1 ;1 0]; 
A*B % 矩陣乘法 
ans = 
0 1 
1 0 
A.*B % A和B對應項相乘 
ans = 
0 0 
0 0 
----------------------------我是分隔線^_^-------------------------- 
3)布朗數組操作(Boolean Array Operations) 
對矩陣的比較運算是數組操作,也就是說,是對每個元素孤立進行的。 
因此其結果就不是一個"真"或者"假",而是一堆"真假"。這個 
結果就是布朗數組。 
D = [-0.2 1.0 1.5 3.0 -1.0 4.2 3.14]; 
D >= 0 
ans = 
0 1 1 1 0 1 1 
如果想選出D中的正元素: 
D = D(D>0) 
D = 
1.0000 1.5000 3.0000 4.2000 3.1400 
除此之外,MATLAB運算中會出現NaN,Inf,-Inf。對它們的比較參見下例 
Inf==Inf返回真 
Inf<1返回假 
NaN==NaN返回假 
同時,可以用isinf,isnan判斷,用法可以顧名思義。 
在比較兩個矩陣大小時,矩陣必須具有相同的尺寸,否則會報錯。這是 
你用的上size和isequal,isequalwithequalnans(R13及以後)。 
-------------------------我是分隔線^_^----------------------------- 
4)從向量構建矩陣(Constructing Matrices from Vectors) 
MATLAB中創建常數矩陣非常簡單,大家經常使用的是: 
A = ones(5,5)*10 
但你是否知道,這個乘法是不必要的? 
A = 10; 
A = A(ones(5,5)) 
A = 
10 10 10 10 10 
10 10 10 10 10 
10 10 10 10 10 
10 10 10 10 10 
10 10 10 10 10 
類似的例子還有: 
v = (1:5)'; 
n = 3; 
M = v(:,ones(n,1)) 
M = 

1 1 1 
2 2 2 
3 3 3 
4 4 4 
5 5 5 
事實上,上述過程還有一種更加容易理解的實現方法: 
A = repmat(10,[5 5]); 
M = repmat([1:5]', [1,3]); 
其中repmat的含義是把一個矩陣重複平鋪,生成較大矩陣。 
更多詳細情況,參見函數repmat和meshgrid。 
----------------------------------------------------- 
5)相關函數列表(Utility Functions) 
ones 全1矩陣 
zeros 全0矩陣 
reshape 修改矩陣形狀 
repmat 矩陣平鋪 
meshgrid 3維plot需要用到的X-Y網格矩陣 
ndgrid n維plot需要用到的X-Y-Z...網格矩陣 
filter 一維數字濾波器,當數組元素前後相關時特別有用。 
cumsum 數組元素的逐步累計 
cumprod 數組元素的逐步累計 
eye 單位矩陣 
diag 生成對角矩陣或者求矩陣對角線 
spdiags 稀疏對角矩陣 
gallery 不同類型矩陣庫 
pascal Pascal 矩陣 
hankel Hankel 矩陣 
toeplitz Toeplitz 矩陣 

========================================================== 
二、擴充的例子 
------------------------------------------------------ 
6)作用於兩個向量的矩陣函數 
假設我們要計算兩個變量的函數F 
F(x,y) = x*exp(-x^2 - y^2) 
我們有一系列x值,保存在x向量中,同時我們還有一系列y值。 
我們要對向量x上的每個點和向量y上的每個點計算F值。換句話 
說,我們要計算對於給定向量x和y的所確定的網格上的F值。 

使用meshgrid,我們可以複製x和y來建立合適的輸入向量。然後 
可以使用第2節中的方法來計算這個函數。 
x = (-2:.2:2); 
y = (-1.5:.2:1.5)'; 
[X,Y] = meshgrid(x, y); 
F = X .* exp(-X.^2 - Y.^2); 
如果函數F具有某些性質,你甚至可以不用meshgrid,比如 
F(x,y) = x*y ,則可以直接用向量外積 
x = (-2:2); 
y = (-1.5:.5:1.5); 
x'*y 
在用兩個向量建立矩陣時,在有些情況下,稀疏矩陣可以更加有 
效地利用存儲空間,並實現有效的算法。我們將在第8節中以一個 
實例來進行更詳細地討論. 
-------------------------------------------------------- 
7)排序、設置和計數(Ordering, Setting, and Counting Operations) 
在迄今爲止討論過的例子中,對向量中一個元素的計算都是獨立 
於同一向量的其他元素的。但是,在許多應用中,你要做的計算 
則可能與其它元素密切相關。例如,假設你用一個向量x來表示一 
個集合。不觀察向量的其他元素,你並不知道某個元素是不是一 
個冗餘元素,並應該被去掉。如何在不使用循環語句的情況下刪除 
冗餘元素,至少在現在,並不是一個明顯可以解決的問題。 

解決這類問題需要相當的智巧。以下介紹一些可用的基本工具 

max 最大元素 
min 最小元素 
sort 遞增排序 
unique 尋找集合中互異元素(去掉相同元素) 
diff 差分運算符[X(2) - X(1), X(3) - X(2), ... X(n) - X(n-1)] 
find 查找非零、非NaN元素的索引值 
union 集合並 
intersect 集合交 
setdiff 集合差 
setxor 集合異或 

繼續我們的實例,消除向量中的多餘元素。注意:一旦向量排序後, 
任何多餘的元素就是相鄰的了。同時,在任何相等的相鄰元素在向量 
diff運算時變爲零。這是我們能夠應用以下策略達到目的。我們現在 
在已排序向量中,選取那些差分非零的元素。 

% 初次嘗試。不太正確! 
x = sort(x(:)); 
difference = diff(x); 
y = x(difference~=0); 

這離正確結果很近了,但是我們忘了diff函數返回向量的元素個數比 
輸入向量少1。在我們的初次嘗試中,沒有考慮到最後一個元素也可能 
是相異的。爲了解決這個問題,我們可以在進行差分之前給向量x加入 
一個元素,並且使得它與以前的元素一定不同。一種實現的方法是增 
加一個NaN。 

% 最終的版本。 
x = sort(x(:)); 
difference = diff([x;NaN]); 
y = x(difference~=0); 

我們使用(:)運算來保證x是一個向量。我們使用~=0運算,而不用find 
函數,因爲find函數不返回NaN元素的索引值,而我們操作中差分的最 
後元素一定是NaN。這一實例還有另一種實現方式: 

y=unique(x); 

後者當然很簡單,但是前者作爲一個練習並非無用,它是爲了練習使用 
矢量化技術,並示範如何編寫你自己的高效代碼。此外,前者還有一個 
作用:Unique函數提供了一些超出我們要求的額外功能,這可能降低代 
碼的執行速度。 

假設我們不只是要返回集合x,而且要知道在原始的矩陣裏每個相異元素 
出現了多少個"複本"。一旦我們對x排序並進行了差分,我們可以用 
find來確定差分變化的位置。再將這個變化位置進行差分,就可以得到 
複本的數目。這就是"diff of find of diff"的技巧。基於以上的討論, 
我們有: 

% Find the redundancy in a vector x 

x = sort(x(:)); 
difference = diff([x;max(x)+1]); 
count = diff(find([1;difference])); 
y = x(find(difference)); 
plot(y,count) 

這個圖畫出了x中每個相異元素出現的複本數。注意,在這裏我們避開了 
NaN,因爲find不返回NaN元素的索引值。但是,作爲特例,NaN和Inf 
的複本數可以容易地計算出來: 

count_nans = sum(isnan(x(:))); 
count_infs = sum(isinf(x(:))); 

另一個用於求和或者計數運算的矢量化技巧是用類似建立稀疏矩陣的方 
法實現的。這還將在第9節中作更加詳細的討論. 
------------------------------------------------------- 
8)稀疏矩陣結構(Sparse Matrix Structures) 

在某些情況下,你可以使用稀疏矩陣來增加計算的效率。如果你構造一 
個大的中間矩陣,通常矢量化更加容易。在某些情況下,你可以充分利 
用稀疏矩陣結構來矢量化代碼,而對於這個中間矩陣不需要大的存儲空 
間。 

假設在上一個例子中,你事先知道集合y的域是整數的子集, 
{k+1,k+2,...k+n};即, 
y = (1:n) + k 

例如,這樣的數據可能代表一個調色板的索引值。然後,你就可以對集 
閤中每個元素的出現進行計數(構建色彩直方圖?譯者)。這是對上一 
節中"diff of find of diff"技巧的一種變形。 


現在讓我們來構造一個大的m x n矩陣A,這裏m是原始x向量中的元素數, 
n是集合y中的元素數。 
A(i,j) = 1 if x(i) = y(j) 
0 otherwise 

回想一下第3節和第4節,你可能認爲我們需要從x和y來構造矩陣A。如果 
當然可以,但要消耗許多存儲空間。我們可以做得更好,因爲我們知道, 
矩陣A中的多數元素爲0,x中的每個元素對應的行上只有一個值爲1。 

以下就是構造矩陣的方法(注意到y(j) = k+j,根據以上的公式): 
x = sort(x(:)); 
A = sparse(1:length(x), x+k, 1, length(x), n); 

現在我們對A的列進行求和,得到出現次數。 
count = sum(A); 
在這種情況下,我們不必明確地形成排序向量y,因爲我們事先知道 
y = 1:n + k. 

這裏的關鍵是使用數據,(也就是說,用x控制矩陣A的結構)。由於x在 
一個已知範圍內取整數值,我們可以更加有效地構造矩陣。 

假設你要給一個很大矩陣的每一列乘以相同的向量。使用稀疏矩陣,不僅 
可以節省空間,並且要比在第5節介紹的方法更加快速. 下面是它的工作 
方式: 
F = rand(1024,1024); 
x = rand(1024,1); 
% 對F的所有行進行點型乘法. 
Y = F * diag(sparse(x)); 
% 對F的所有列進行點型乘法. 
Y = diag(sparse(x)) * F; 

我們充分利用矩陣乘法算符來執行大規模運算,並使用稀疏矩陣以防止臨 
時變量變得太大。 
-------------------------------------------------------- 
9)附加的例子(Additional Examples) 
下面的例子使用一些在本技術手冊中討論過的方法,以及其它一些相關方 
法。請嘗試使用tic 和toc(或t=cputime和cputime-t),看一下速度加快 
的效果。 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

用於計算數組的雙重for循環。 
使用的工具:數組乘法 
優化前: 
A = magic(100); 
B = pascal(100); 
for j = 1:100 
for k = 1:100; 
X(j,k) = sqrt(A(j,k)) * (B(j,k) - 1); 
end 
end 
優化後: 
A = magic(100); 
B = pascal(100); 
X = sqrt(A).*(B-1); 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

用一個循環建立一個向量,其元素依賴於前一個元素 
使用的工具:FILTER, CUMSUM, CUMPROD 
優化前: 
A = 1; 
L = 1000; 
for i = 1:L 
A(i+1) = 2*A(i)+1; 
end 
優化後: 
L = 1000; 
A = filter([1],[1 -2],ones(1,L+1)); 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

如果你的向量構造只使用加法或乘法,你可使用cumsum或cumprod函數。 
優化前: 
n=10000; 
V_B=100*ones(1,n); 
V_B2=100*ones(1,n); 
ScaleFactor=rand(1,n-1); 
for i = 2:n 
V_B(i) = V_B(i-1)*(1+ScaleFactor(i-1)); 
end 
for i=2:n 
V_B2(i) = V_B2(i-1)+3; 
end 
優化後: 
n=10000; 
V_A=100*ones(1,n); 
V_A2 = 100*ones(1,n); 
ScaleFactor=rand(1,n-1); 
V_A=cumprod([100 1+ScaleFactor]); 
V_A2=cumsum([100 3*ones(1,n-1)]); 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

向量累加,每5個元素進行一次: 
工具:CUMSUM , 向量索引 
優化前: 
% Use an arbitrary vector, x 
x = 1:10000; 
y = []; 
for n = 5:5:length(x) 
y = [y sum(x(1:n))]; 
end 
優化後(使用預分配): 
x = 1:10000; 
ylength = (length(x) - mod(length(x),5))/5; 
% Avoid using ZEROS command during preallocation 
y(1:ylength) = 0; 
for n = 5:5:length(x) 
y(n/5) = sum(x(1:n)); 
end 
優化後(使用矢量化,不再需要預分配): 
x = 1:10000; 
cums = cumsum(x); 
y = cums(5:5:length(x)); 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


操作一個向量,當某個元素的後繼元素爲0時,重複這個元素: 
工具:FIND, CUMSUM, DIFF 
任務:我們要操作一個由非零數值和零組成的向量,要求把零替換成爲 
它前面的非零數值。例如,我們要轉換下面的向量: 
a=2; b=3; c=5; d=15; e=11; 
x = [a 0 0 0 b 0 0 c 0 0 0 0 d 0 e 0 0 0 0 0]; 
爲: 
x = [a a a a b b b c c c c c d d e e e e e e]; 
解(diff和cumsum是反函數): 
valind = find(x); 
x(valind(2:end)) = diff(x(valind)); 
x = cumsum(x); 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

將向量的元素累加到特定位置上 
工具:SPARSE 
優化前: 
% The values we are summing at designated indices 
values = [20 15 45 50 75 10 15 15 35 40 10]; 
% The indices associated with the values are summed cumulatively 
indices = [2 4 4 1 3 4 2 1 3 3 1]; 
totals = zeros(max(indices),1); 
for n = 1:length(indices) 
totals(indices(n)) = totals(indices(n)) + values(n); 
end 
優化後: 
indices = [2 4 4 1 3 4 2 1 3 3 1]; 
totals = full(sparse(indices,1,values)); 

注意:這一方法開闢了稀疏矩陣的新用途。在使用sparse命令創建稀疏矩陣 
時,它是對分配到同一個索引的所有值求和,而不是替代已有的數值。這稱 
爲"向量累加",是MATLAB處理稀疏矩陣的方式。 

======================================================================== 

三、更多資源 
-------------------------------------------------------------- 
10)矩陣索引和運算 
下面的MATLAB文摘討論矩陣索引。它比本技術手冊的第1節提供了更加 
詳細的信息 
MATLAB Digest: Matrix Indexing in MATLAB 
http://www.mathworks.com/company/digest/sept01/matrix.shtml 

下面的說明鏈接將指導你如何使用MATLAB中的矩陣操作。含括矩陣創建 
、索引、操作、數組運算、矩陣運算以及其它主題。 
MATLAB Documentation: Getting Started 
http://www.mathworks.com/access/helpdesk/help/techdoc/ 
learn_MATLAB/learn_MATLAB.shtml 

Peter Acklam是我們的一個power user,他建立了一個網頁提供MATLAB 
數組處理中的一些技巧。 
Peter Acklam's MATLAB array manipulation tips and tricks 
http://home.online.no/~pjacklam/MATLAB/doc/mtt/index.html 
--------------------------------------------------------------- 
11)矩陣存儲管理(Matrix Memory Management) 

有關預分配技術如何加快計算速度的更多的信息,參見下面的聯機解決方案: 

26623: How do I pre-allocate memory when using MATLAB
http://www.mathworks.com/support/solutions/data/26623.shtml 

下面的技術手冊是關於MATLAB存儲管理方面的一個包羅廣泛的指南: 
The Technical Support Guide to Memory Management 
http://www.mathworks.com/support/tech-notes/1100/1106.shtml 
---------------------------------------------------------------- 
12)發揮MATLAB的最高性能(Maximizing MATLAB Performance) 

這個技術手冊對代碼矢量化進行一般的討論,而許多時候,我們的目的是加快 

代碼的速度。因此,本節提供一些附加的資源,它們涉及如何是代碼達到最高 

性能。 

加速代碼的常用技巧: 
3257: How do I increase the speed or performance 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章