建、索引到運(yùn)算與排錯(cuò))
這期繼續(xù)更MATLAB基礎(chǔ)學(xué)習(xí)筆記主題是向量。前兩篇分別把基本操作和矩陣、數(shù)組的概念理了一遍這次專門(mén)把向量單獨(dú)拎出來(lái)聊原因是很多初學(xué)者在真正動(dòng)手寫(xiě)腳本時(shí)第一個(gè)卡住的地方往往不是矩陣操作而是“向量到底該怎么建、怎么取、怎么算”。向量這個(gè)知識(shí)點(diǎn)看著小但它貫穿了數(shù)據(jù)預(yù)處理、信號(hào)處理、控制系統(tǒng)仿真甚至圖像處理的每一個(gè)角落可以說(shuō)把向量吃透了MATLAB的學(xué)習(xí)就能順暢一大半。這篇筆記適合兩類讀者一類是剛接觸MATLAB、準(zhǔn)備系統(tǒng)入門(mén)的人另一類是已經(jīng)會(huì)用plot和for循環(huán)、但碰到索引報(bào)錯(cuò)和維度不匹配就蒙圈的人。我會(huì)從向量的底層認(rèn)知講起一直講到創(chuàng)建、運(yùn)算、索引、可視化和常見(jiàn)報(bào)錯(cuò)盡量把那些“書(shū)上不寫(xiě)但實(shí)戰(zhàn)中一定會(huì)遇到”的細(xì)節(jié)都攤開(kāi)說(shuō)清楚。內(nèi)容偏實(shí)操每個(gè)知識(shí)點(diǎn)都配了可以直接復(fù)制運(yùn)行的小例子建議你打開(kāi)MATLAB一邊看一邊敲。1. 先理解向量矩陣視角下的行與列1.1 一切皆矩陣向量只是“退化”的矩陣先說(shuō)一個(gè)底層認(rèn)知。MATLAB這個(gè)名字本身就來(lái)自Matrix Laboratory它的核心數(shù)據(jù)結(jié)構(gòu)就是矩陣標(biāo)量是1×1的矩陣向量是1×n或者n×1的矩陣矩陣就是m×n的完整數(shù)組。很多報(bào)錯(cuò)你之所以看不懂是因?yàn)槟X子里還在把向量當(dāng)成“一列數(shù)據(jù)”而MATLAB眼里只有“矩陣的尺寸”和“矩陣乘法的規(guī)則”。舉個(gè)例子你在MATLAB里寫(xiě)x [1 2 3]它的形態(tài)是1行3列也就是行向量你寫(xiě)x [1; 2; 3]它的形態(tài)是3行1列是列向量。這兩種形態(tài)在屏幕上看起來(lái)都是“一串?dāng)?shù)字”但在矩陣運(yùn)算里的意義完全不同。判斷一個(gè)變量的真實(shí)尺寸用size()函數(shù)就行了x_row [1 2 3]; x_col [1; 2; 3]; size(x_row) % 結(jié)果為 1 3 size(x_col) % 結(jié)果為 3 1我在帶新人時(shí)經(jīng)常做一個(gè)練習(xí)讓他們先用size打印出腳本中每個(gè)變量的維度再解釋為什么某個(gè)運(yùn)算會(huì)報(bào)錯(cuò)。這個(gè)習(xí)慣非常有用尤其是當(dāng)你從別人手里接過(guò)來(lái)一段代碼時(shí)百分之八十的報(bào)錯(cuò)都可以靠打印維度排查掉。理解了“一切皆矩陣”你再回頭看length和numel的區(qū)別就很容易了。length(x)返回的是最長(zhǎng)那個(gè)維度的長(zhǎng)度numel(x)返回的是元素總個(gè)數(shù)。對(duì)一個(gè)向量來(lái)說(shuō)兩者相等但一旦變量變成矩陣length就可能不再是你想要的那個(gè)數(shù)。1.2 行向量與列向量方向決定命運(yùn)創(chuàng)建行向量和列向量的方式表面上只差一個(gè)分號(hào)或轉(zhuǎn)置但在實(shí)際計(jì)算中這個(gè)方向會(huì)直接影響運(yùn)算結(jié)果。行向量用空格或逗號(hào)分隔元素列向量用分號(hào)分隔比如a [1 2 3]; % 1行3列 b [1; 2; 3]; % 3行1列 c a; % 轉(zhuǎn)置成列向量 d b.; % 轉(zhuǎn)置成行向量這里有個(gè)非常重要的細(xì)節(jié)和.在實(shí)數(shù)向量上效果一樣都是轉(zhuǎn)置但在復(fù)數(shù)向量上它們完全不同。是共軛轉(zhuǎn)置會(huì)把虛部符號(hào)翻轉(zhuǎn).是普通轉(zhuǎn)置不會(huì)改變?cè)刂?。后面我?huì)專門(mén)講復(fù)向量的問(wèn)題這里你先記住一個(gè)原則如果你只是想把行變列、列變行而不是做數(shù)學(xué)意義上的共軛那就統(tǒng)一用.這樣無(wú)論未來(lái)處理什么數(shù)據(jù)都不會(huì)踩坑。行向量與列向量的“方向”還會(huì)影響隱式擴(kuò)展和矩陣乘法。舉個(gè)常見(jiàn)的例子如果你想把一組數(shù)據(jù)和另一個(gè)向量做點(diǎn)運(yùn)算方向的差異會(huì)導(dǎo)致維度不匹配。我在實(shí)際項(xiàng)目中見(jiàn)過(guò)有人因?yàn)閷?xiě)漏了一個(gè)轉(zhuǎn)置符號(hào)導(dǎo)致整個(gè)信號(hào)濾波結(jié)果被轉(zhuǎn)置到相反方向調(diào)試了整整一個(gè)下午。所以每當(dāng)你創(chuàng)建向量時(shí)心里要先問(wèn)一句我到底要的是1×n還是n×11.3 復(fù)向量帶來(lái)的共軛轉(zhuǎn)置陷阱復(fù)向量在信號(hào)處理和通信系統(tǒng)里非常常見(jiàn)這時(shí)和.的差異就不是紙上談兵了??磦€(gè)具體例子z [12i, 3-4i, 56i]; z1 z; % 共軛轉(zhuǎn)置元素變成 [1-2i; 34i; 5-6i] z2 z.; % 普通轉(zhuǎn)置元素還是 [12i; 3-4i; 56i]只是方向變了如果是在做FFT頻譜分析你拿到了復(fù)數(shù)頻譜想把它從行向量轉(zhuǎn)成列向量再拼到矩陣?yán)锊恍⌒挠昧四敲刺摬糠?hào)就全變了后續(xù)的相位分析全部作廢而且這種錯(cuò)誤很難用肉眼看出來(lái)因?yàn)槟V祹缀鯖](méi)變化只有相位不對(duì)。很多老手也習(xí)慣寫(xiě)是因?yàn)樵缙贛ATLAB版本里人們?;煊玫珡奈易约旱慕?jīng)驗(yàn)看絕不要在圖省事的地方埋這種雷。2. 創(chuàng)建向量從冒號(hào)到函數(shù)批量生成2.1 冒號(hào)運(yùn)算符最常用的等間隔向量創(chuàng)建向量最基礎(chǔ)也最常用的方式就是冒號(hào)運(yùn)算符。語(yǔ)法是起始值:步長(zhǎng):結(jié)束值步長(zhǎng)省略時(shí)默認(rèn)為1。直接看例子x1 1:5; % [1 2 3 4 5] x2 0:0.2:1; % [0 0.2 0.4 0.6 0.8 1] x3 5:-1:1; % [5 4 3 2 1] x4 1:0.5:0; % 空向量因?yàn)椴介L(zhǎng)為正且起始值大于結(jié)束值這里需要提醒一個(gè)新手很容易忽略的點(diǎn)冒號(hào)的步長(zhǎng)可以是負(fù)數(shù)但方向必須和起始到結(jié)束的方向一致。如果你想生成一個(gè)遞減向量卻忘了寫(xiě)負(fù)步長(zhǎng)得到的不是報(bào)錯(cuò)而是一個(gè)空向量??障蛄勘旧聿粓?bào)錯(cuò)但當(dāng)你后續(xù)用x(1)去訪問(wèn)它的第一個(gè)元素時(shí)就會(huì)彈出索引越界的錯(cuò)誤這種問(wèn)題在真實(shí)代碼里隱蔽性極高。另外浮點(diǎn)步長(zhǎng)有一個(gè)潛在風(fēng)險(xiǎn)。像是0:0.1:1這種寫(xiě)法雖然大多數(shù)情況下能得到11個(gè)元素但由于浮點(diǎn)數(shù)無(wú)法精確表示0.1個(gè)別版本或邊界條件下可能會(huì)出現(xiàn)最后一點(diǎn)不是1的情況。我個(gè)人的習(xí)慣是如果明確知道需要多少個(gè)等間隔點(diǎn)就用linspace如果只是想用步長(zhǎng)生成一個(gè)粗糙的采樣序列冒號(hào)運(yùn)算符也沒(méi)問(wèn)題但不要在浮點(diǎn)步長(zhǎng)的向量上做精確等式判斷比如x(11) 1這種很容易得到邏輯假。2.2 linspace與logspace等分與對(duì)數(shù)間隔linspace用來(lái)生成指定起點(diǎn)和終點(diǎn)之間固定數(shù)量的等間隔點(diǎn)語(yǔ)法是linspace(起始, 終點(diǎn), 點(diǎn)數(shù))。點(diǎn)數(shù)不寫(xiě)時(shí)默認(rèn)是100。它在畫(huà)圖時(shí)特別常用因?yàn)槔盟梢苑浅>_地控制采樣個(gè)數(shù)t linspace(0, 2*pi, 200); % 200個(gè)點(diǎn)從0到2*pi均勻分布 y sin(t); plot(t, y);你可能會(huì)問(wèn)既然冒號(hào)運(yùn)算符也能生成等間隔向量為什么要用linspace兩個(gè)原因。第一linspace的點(diǎn)數(shù)是確定的你不用去算步長(zhǎng)第二linspace內(nèi)部會(huì)把首尾點(diǎn)都包含進(jìn)去對(duì)于需要包含端點(diǎn)的場(chǎng)合比如設(shè)置坐標(biāo)軸范圍更加可靠。典型情況是你要畫(huà)一個(gè)光滑的正弦曲線用冒號(hào)寫(xiě)0:0.01:2*pi大約生成629個(gè)點(diǎn)但終點(diǎn)因?yàn)楦↑c(diǎn)誤差不一定精確落在2*pi上用linspace(0, 2*pi, 630)就能精確定位。logspace和linspace類似但它生成的是對(duì)數(shù)間隔的點(diǎn)常用于頻率軸。logspace(-2, 2, 5)會(huì)生成從0.01到100之間按10的冪分布的5個(gè)點(diǎn)也就是0.01、0.1、1、10、100。在做伯德圖、頻響分析或者取采樣頻率的對(duì)數(shù)刻度的場(chǎng)景中l(wèi)ogspace幾乎是標(biāo)配。2.3 zeros、ones、rand等函數(shù)批量生成除了手工列舉元素更實(shí)用的是用函數(shù)一次性生成向量。下面這幾個(gè)函數(shù)是我平時(shí)用得最多的a zeros(1, 5); % [0 0 0 0 0] b ones(3, 1); % [1; 1; 1] c rand(1, 10); % 10個(gè)[0,1)均勻隨機(jī)數(shù) d randn(1, 10); % 10個(gè)標(biāo)準(zhǔn)正態(tài)隨機(jī)數(shù) e randi([1, 100], 1, 8); % 8個(gè)1到100之間的隨機(jī)整數(shù)需要特別說(shuō)明的是randi的第三、第四個(gè)參數(shù)分別代表行數(shù)和列數(shù)千萬(wàn)別把順序搞反。我見(jiàn)過(guò)有人想要10個(gè)1到100的隨機(jī)整數(shù)結(jié)果寫(xiě)成了randi([1, 100], 10)得到的是10×10的矩陣而不是向量后續(xù)運(yùn)算全部亂套。repmat和repelem也值得一提它們都能復(fù)制向量但復(fù)制邏輯不同。repmat(a, 1, 3)把整個(gè)向量當(dāng)作原子來(lái)重復(fù)結(jié)果是[1 2 3 1 2 3 1 2 3]repelem(a, 2)則把每個(gè)元素分別重復(fù)結(jié)果是[1 1 2 2 3 3]。這兩個(gè)函數(shù)在矩陣擴(kuò)展和樣本生成時(shí)非常實(shí)用但很多人到了一兩年后才開(kāi)始使用其實(shí)早期學(xué)會(huì)能省很多循環(huán)。3. 向量的三種核心運(yùn)算點(diǎn)運(yùn)算、點(diǎn)積叉積、范數(shù)歸一化3.1 元素級(jí)運(yùn)算與矩陣運(yùn)算的區(qū)別這是MATLAB新人最大的分水嶺要不要在運(yùn)算符前面加那個(gè)點(diǎn)。*是矩陣乘法.*是元素級(jí)乘法/和./、^和.^同理。矩陣乘法要求內(nèi)維度匹配而元素級(jí)運(yùn)算要求兩個(gè)向量形狀完全一致或者滿足隱式擴(kuò)展條件。打個(gè)比方。矩陣乘法像“兩個(gè)隊(duì)伍按規(guī)則配對(duì)”行向量的每個(gè)元素要和列向量的每個(gè)元素組合求積所以1×3的行向量不能直接乘以1×3的行向量?jī)?nèi)維度不匹配。元素級(jí)運(yùn)算像“兩個(gè)人面對(duì)面結(jié)對(duì)手拉手”對(duì)應(yīng)位置的元素逐一相乘所以只要兩個(gè)向量尺寸一致就能算。看這段代碼a [1 2 3]; b [4 5 6]; c a .* b; % [4 10 18]逐元素相乘 d a * b; % 32矩陣乘法內(nèi)積 e a * b; % 報(bào)錯(cuò)Inner matrix dimensions must agree.我指導(dǎo)過(guò)很多學(xué)生他們第一次寫(xiě)向量?jī)?nèi)積時(shí)都習(xí)慣寫(xiě)成a * b然后被報(bào)錯(cuò)嚇到。其實(shí)此時(shí)要么寫(xiě)成a * b要么用dot(a, b)。而如果你只是想對(duì)兩個(gè)等長(zhǎng)向量的每個(gè)對(duì)應(yīng)位置做運(yùn)算就老老實(shí)實(shí)加個(gè)點(diǎn)。判斷標(biāo)準(zhǔn)很簡(jiǎn)單你關(guān)心的是“整體組合”還是“逐個(gè)對(duì)應(yīng)”前者用矩陣運(yùn)算后者用點(diǎn)運(yùn)算。3.2 點(diǎn)積與叉積的工程含義MATLAB里直接用dot和cross函數(shù)不用自己寫(xiě)求和公式。點(diǎn)積也叫內(nèi)積公式上等于sum(a .* b)幾何意義是一個(gè)向量在另一個(gè)向量方向上的投影長(zhǎng)度乘以另一個(gè)向量的長(zhǎng)度。在數(shù)據(jù)分析里點(diǎn)積經(jīng)常用來(lái)衡量?jī)蓚€(gè)序列的相關(guān)程度。計(jì)算兩個(gè)傳感器信號(hào)的點(diǎn)積如果值很大說(shuō)明波形趨勢(shì)相近如果接近零說(shuō)明兩者幾乎正交。a [1, 2, 3]; b [4, 5, 6]; p dot(a, b); % 32 p2 sum(a .* b); % 同樣為32叉積則不同cross(a, b)返回一個(gè)垂直于a和b所張成平面的新向量方向由右手定則決定。三維空間中叉積常用于求法向量、旋轉(zhuǎn)軸。例如已知平面上兩個(gè)不平行向量取它們的叉積就能得到該平面的法向量這在計(jì)算機(jī)圖形學(xué)里非常常用。二維向量也能用cross它返回一個(gè)標(biāo)量表示“有向面積”的正負(fù)可以用來(lái)判斷三個(gè)點(diǎn)構(gòu)成的角度是順時(shí)針還是逆時(shí)針p1 [0, 0]; p2 [1, 0]; p3 [0, 1]; c cross([p2-p1], [p3-p1]); % 返回正數(shù)說(shuō)明逆時(shí)針3.3 范數(shù)與歸一化范數(shù)用來(lái)衡量向量的大小。常見(jiàn)的三種是1范數(shù)絕對(duì)值之和、2范數(shù)歐幾里得長(zhǎng)度、無(wú)窮范數(shù)最大絕對(duì)值。MATLAB里直接norm(v, 1)、norm(v, 2)或者norm(v)默認(rèn)2范數(shù)、norm(v, Inf)即可。v [3, 4, 0]; n2 norm(v); % 5 n1 norm(v, 1); % 7 ninf norm(v, Inf); % 4歸一化就是把向量按比例縮放到單位長(zhǎng)度或特定范圍常用兩種方式。一種是讓向量的2范數(shù)變?yōu)?即normalize(v, norm)另一種是把元素線性映射到[0, 1]區(qū)間即normalize(v, range)。這個(gè)操作在機(jī)器學(xué)習(xí)數(shù)據(jù)預(yù)處理里幾乎必做因?yàn)槿绻粴w一化量綱差異大的特征會(huì)直接壓過(guò)其他特征比如一個(gè)范圍是0到10000的變量和一個(gè)范圍是0到1的變量放在一起算距離后者基本不起作用。v [10, 20, 30]; u normalize(v, norm); % 模長(zhǎng)為1的向量 r normalize(v, range); % [0, 0.5, 1]這里有個(gè)細(xì)節(jié)容易被忽略normalize函數(shù)返回的結(jié)果在R2018a之后可用如果你的MATLAB版本比較老可以用手寫(xiě)方式代替比如v ./ norm(v)和(v - min(v)) ./ (max(v) - min(v))。效果完全一樣。4. 索引與切片數(shù)據(jù)快速定位的多種技巧4.1 下標(biāo)索引、end與步長(zhǎng)MATLAB的索引從1開(kāi)始這一點(diǎn)和Python的0起始完全不同初學(xué)階段最容易出低級(jí)錯(cuò)誤?;居梅ㄓ邢旅鎺追Nx [10, 20, 30, 40, 50]; x(2); % 20單元素索引 x(1:3); % [10 20 30]連續(xù)切片 x(2:2:end); % [20 40]帶步長(zhǎng)的切片 x(end); % 50最后一個(gè)元素 x(end:-1:1); % [50 40 30 20 10]倒序end是一個(gè)隱式的索引關(guān)鍵字它等價(jià)于當(dāng)前維度的長(zhǎng)度。用end的好處是即使向量長(zhǎng)度變化代碼依然自動(dòng)適配。我自己的習(xí)慣是只要涉及數(shù)組末端位置一律用end而不是硬編碼一個(gè)具體數(shù)字比如x(x中元素的個(gè)數(shù))這種寫(xiě)法的維護(hù)成本太高一旦前面的處理改變了向量長(zhǎng)度后邊的索引就要跟著改。這里還要提醒一句MATLAB沒(méi)有負(fù)數(shù)索引。x(-1)不會(huì)像Python那樣返回倒數(shù)第一個(gè)元素而是直接報(bào)錯(cuò)“數(shù)組索引必須為正整數(shù)或邏輯值”。很多人從Python轉(zhuǎn)過(guò)來(lái)都會(huì)在這上面浪費(fèi)幾分鐘。4.2 邏輯索引一步篩出你想要的數(shù)據(jù)邏輯索引是MATLAB非常強(qiáng)大但新手不太適應(yīng)的一種索引方式。它的核心思想是用一個(gè)與向量等長(zhǎng)的邏輯數(shù)組由true和false組成作為索引只有對(duì)應(yīng)位置為true的元素才會(huì)被取出。寫(xiě)起來(lái)非常直觀data randn(1, 100); outliers data(abs(data) 2); % 直接取所有絕對(duì)值大于2的異常點(diǎn) pos data(data 0); % 取所有正數(shù) idx find(data 0); % 或者只取位置下標(biāo)這里有兩套索引體系位置下標(biāo)索引比如data([1, 5, 7])和邏輯索引比如data(data 0)。邏輯索引的價(jià)值在于它把“條件判斷”和“取數(shù)”合到了一步代碼既短又不容易出錯(cuò)。如果你需要下標(biāo)再用find函數(shù)轉(zhuǎn)換。在實(shí)際項(xiàng)目里我用邏輯索引做數(shù)據(jù)清洗的比例非常高比如從傳感器采集序列中剔除掉無(wú)效的NaN值valid data(~isnan(data) ~isinf(data));這一行就完成了傳統(tǒng)寫(xiě)法里好幾行的循環(huán)判斷這就是向量化思維帶來(lái)的效率。4.3 修改、插入與刪除元素向量的修改和擴(kuò)展很靈活但有幾個(gè)隱藏行為必須知道。先看基本的x [1, 2, 3, 4]; x(2) 99; % 修改第二個(gè)元素[1 99 3 4] x(end 1) 100; % 末尾追加[1 99 3 4 100] x [x(1), 50, x(2:end)]; % 在第二個(gè)位置插入50 x(3) []; % 刪除第三個(gè)元素重點(diǎn)說(shuō)一下那個(gè)隱藏坑如果你直接給一個(gè)中間跳空的位置賦值MATLAB不會(huì)報(bào)錯(cuò)而是自動(dòng)用0填充跳躍部分。比如x [10, 20, 30]; x(5) 99;得到的x是[10 20 30 0 99]。這個(gè)行為在很多情況下不是你想要的尤其是從循環(huán)里動(dòng)態(tài)組裝數(shù)據(jù)時(shí)一旦索引寫(xiě)錯(cuò)會(huì)悄無(wú)聲息地多出一堆0后續(xù)算均值、找峰值全都會(huì)受影響。排查這種bug比報(bào)錯(cuò)要痛苦得多因?yàn)槌绦颉罢_\(yùn)行”但結(jié)果錯(cuò)了。所以在動(dòng)態(tài)擴(kuò)展向量時(shí)要么用end 1的方式每次都追加到末尾要么干脆初始化一個(gè)足夠長(zhǎng)的零向量再按位置填充。5. 可視化與向量化編程5.1 plot、stem與直方圖把向量畫(huà)出來(lái)向量的可視化是驗(yàn)證算法正確性的最快方式。最基本的plot用法是橫坐標(biāo)向量加縱坐標(biāo)向量t linspace(0, 2*pi, 200); y1 sin(t); y2 cos(t); plot(t, y1, r-, LineWidth, 2); hold on; plot(t, y2, b--); legend(sin, cos); xlabel(t); ylabel(y); grid on;需要注意的一點(diǎn)是plot的兩個(gè)參數(shù)必須是同尺寸的向量橫縱都行但如果一個(gè)是行向量另一個(gè)是列向量在舊版本里會(huì)得到一堆雜亂曲線而不是一條線?,F(xiàn)在的新版本支持隱式擴(kuò)展會(huì)自動(dòng)調(diào)整但為了可讀性還是建議畫(huà)圖前統(tǒng)一方向。對(duì)于離散數(shù)據(jù)stem比plot更合適要觀察分布形態(tài)直接用histogramr randn(1, 10000); histogram(r, 50);這個(gè)圖一看就能判斷數(shù)據(jù)是否接近正態(tài)分布比你計(jì)算任何統(tǒng)計(jì)量都直觀。我在做信號(hào)分析時(shí)幾乎每次拿到一段新數(shù)據(jù)的第一件事就是畫(huà)圖先看形態(tài)再?zèng)Q定用什么算法處理。可視化不是為了發(fā)朋友圈是為了幫你確認(rèn)數(shù)據(jù)的“脾氣”。5.2 向量化思維用內(nèi)置函數(shù)取代for循環(huán)向量化是MATLAB性能優(yōu)化里最重要的一課。因?yàn)镸ATLAB本質(zhì)是解釋型語(yǔ)言純循環(huán)的開(kāi)銷很大而內(nèi)置函數(shù)背后是高度優(yōu)化的C和Fortran代碼還利用了現(xiàn)代CPU的并行能力。比如你要計(jì)算序列的累積和可以寫(xiě)循環(huán)x 1:100000; y zeros(size(x)); for k 1:numel(x) y(k) sum(x(1:k)); end但一行cumsum(x)就能做到同樣的事速度可能快幾十倍。類似的還有diff用來(lái)差分、abs取絕對(duì)值、sin和cos直接對(duì)整個(gè)向量操作、exp和log也能直接用。新建向量時(shí)不要寫(xiě)y[]然后循環(huán)里逐元素添加那是最慢的寫(xiě)法學(xué)會(huì)把“逐點(diǎn)計(jì)算”翻譯成“對(duì)整個(gè)數(shù)組的一次函數(shù)調(diào)用”。我自己在帶項(xiàng)目時(shí)做過(guò)一個(gè)簡(jiǎn)單的性能測(cè)試對(duì)100萬(wàn)個(gè)隨機(jī)數(shù)分別用循環(huán)和向量化計(jì)算exp(x).*sin(x)的和向量化版本運(yùn)行時(shí)間常常不到循環(huán)版的十分之一。性能差異在幾百萬(wàn)點(diǎn)的數(shù)據(jù)上非常明顯這在寫(xiě)實(shí)時(shí)處理腳本時(shí)是生死攸關(guān)的問(wèn)題。不過(guò)也要說(shuō)句公道話不是所有場(chǎng)景都適合向量化比如遞歸算法或者每個(gè)新值依賴前一個(gè)值的迭代過(guò)程硬要向量化只會(huì)把代碼寫(xiě)得像天書(shū)這時(shí)候用循環(huán)反而清晰。6. 常見(jiàn)報(bào)錯(cuò)與排錯(cuò)實(shí)錄6.1 維度不匹配先查size再做運(yùn)算最常見(jiàn)的報(bào)錯(cuò)就是Error using *和Inner matrix dimensions must agree.。這條報(bào)錯(cuò)幾乎可以翻譯成一句話你把矩陣乘法用在了不該用的地方或者向量的方向不對(duì)。處理思路分三步第一用size打印出參與運(yùn)算的各變量維度第二確認(rèn)你到底想要矩陣乘法還是元素級(jí)運(yùn)算第三如果是想得到標(biāo)量?jī)?nèi)積但兩個(gè)都是行向量記得用a * b或dot(a, b)。還有一類維度不匹配是拼接時(shí)產(chǎn)生的比如[a, b]要求a和b都是行向量且列數(shù)兼容如果一個(gè)是行向量一個(gè)是列向量就會(huì)報(bào)錯(cuò)或者產(chǎn)生一個(gè)意外形狀的矩陣。我會(huì)在所有讀入數(shù)據(jù)的腳本里加一行disp(size(data))確保從一開(kāi)始就知道數(shù)據(jù)形態(tài)后邊才不會(huì)出連環(huán)錯(cuò)。6.2 索引越界與空向量為什么x(1)也會(huì)報(bào)錯(cuò)報(bào)錯(cuò)文本Index exceeds the number of array elements.意味著你訪問(wèn)的下標(biāo)大于當(dāng)前向量長(zhǎng)度。這種情況經(jīng)常發(fā)生在循環(huán)里循環(huán)邊界用了導(dǎo)致多走了一位或者冒號(hào)方向?qū)懛瓷闪丝障蛄吭偃ピL問(wèn)空向量的第一個(gè)元素??障蛄吭贛ATLAB里是合法的size([])結(jié)果是0×0但你訪問(wèn)x(1)就會(huì)報(bào)錯(cuò)。排查技巧是報(bào)錯(cuò)那一行之前先打印numel(x)和出問(wèn)題的索引值。比如k 5; disp(numel(x)); disp(k); x(k);這樣能立刻看出是索引超出了真實(shí)長(zhǎng)度還是循環(huán)計(jì)數(shù)本身就錯(cuò)了。另一個(gè)容易被忽略的場(chǎng)景是find找不到任何滿足條件的元素它返回空數(shù)組然后你用這個(gè)空數(shù)組去索引原向量同樣會(huì)報(bào)錯(cuò)。我習(xí)慣在find之后先判斷一下是否為空再?zèng)Q定后續(xù)操作。6.3 隱式擴(kuò)展與預(yù)分配兩個(gè)提升性能和穩(wěn)定性的習(xí)慣新版MATLAB支持隱式擴(kuò)展也就是允許一個(gè)向量和一個(gè)標(biāo)量直接運(yùn)算比如[1 2 3] 1會(huì)得到[2 3 4]不需要手動(dòng)寫(xiě)ones(1,3)去擴(kuò)展。這大大方便了日常計(jì)算但也有一些反面教訓(xùn)。當(dāng)你寫(xiě)a [1 2 3]; b [4; 5; 6]; a b時(shí)隱式擴(kuò)展會(huì)把a(bǔ)擴(kuò)展成3×3、把b也擴(kuò)展成3×3得到3×3矩陣。這個(gè)行為在舊版本里會(huì)直接報(bào)錯(cuò)現(xiàn)在卻“正常執(zhí)行”了。如果你沒(méi)意識(shí)到這一點(diǎn)后續(xù)代碼可能收到一個(gè)預(yù)期之外的矩陣。所以算完一個(gè)表達(dá)式后打印一下size仍然是排查問(wèn)題的好習(xí)慣。預(yù)分配指的是在循環(huán)前先創(chuàng)建好目標(biāo)向量比如n 10000; y zeros(1, n); for k 1:n y(k) k^2; end而不是在循環(huán)內(nèi)部用y [y, k^2]不斷讓向量變長(zhǎng)。動(dòng)態(tài)增長(zhǎng)會(huì)讓MATLAB反復(fù)重新分配內(nèi)存數(shù)據(jù)量一大性能直線下降。我在寫(xiě)仿真腳本比如隨機(jī)游走模型或逐點(diǎn)信號(hào)的遞推計(jì)算時(shí)都強(qiáng)制自己先zeros預(yù)分配這個(gè)習(xí)慣在數(shù)據(jù)量達(dá)到百萬(wàn)級(jí)別時(shí)能明顯感覺(jué)到差異。還有一個(gè)相關(guān)的坑是直接給跳空位置賦值造成自動(dòng)補(bǔ)0前面已經(jīng)強(qiáng)調(diào)過(guò)這里不再重復(fù)。這篇筆記按我自己踩坑的順序整理了一遍。最后分享一個(gè)我一直保留的小習(xí)慣每寫(xiě)完一段向量相關(guān)代碼先用size打印一下關(guān)鍵變量的維度尤其是在轉(zhuǎn)置、拼接、邏輯索引之后。維度對(duì)了后面基本不會(huì)出大亂子。初學(xué)的朋友可以自己做個(gè)小練習(xí)生成1000個(gè)隨機(jī)數(shù)分別用循環(huán)和向量化方式計(jì)算exp(x).*sin(x)的和對(duì)比運(yùn)行時(shí)間你會(huì)有非常直觀的感受。向量是MATLAB一切計(jì)算的地基這關(guān)打牢之后再去學(xué)矩陣運(yùn)算、機(jī)器學(xué)習(xí)算法或者圖像處理都會(huì)順暢很多。