ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

MATLAB向量完全指南:从创建、索引到运算与排错

MATLAB向量完全指南:从创建、索引到运算与排错 这期继续更MATLAB基础学习笔记主题是向量。前两篇分别把基本操作和矩阵、数组的概念理了一遍这次专门把向量单独拎出来聊原因是很多初学者在真正动手写脚本时第一个卡住的地方往往不是矩阵操作而是“向量到底该怎么建、怎么取、怎么算”。向量这个知识点看着小但它贯穿了数据预处理、信号处理、控制系统仿真甚至图像处理的每一个角落可以说把向量吃透了MATLAB的学习就能顺畅一大半。这篇笔记适合两类读者一类是刚接触MATLAB、准备系统入门的人另一类是已经会用plot和for循环、但碰到索引报错和维度不匹配就蒙圈的人。我会从向量的底层认知讲起一直讲到创建、运算、索引、可视化和常见报错尽量把那些“书上不写但实战中一定会遇到”的细节都摊开说清楚。内容偏实操每个知识点都配了可以直接复制运行的小例子建议你打开MATLAB一边看一边敲。1. 先理解向量矩阵视角下的行与列1.1 一切皆矩阵向量只是“退化”的矩阵先说一个底层认知。MATLAB这个名字本身就来自Matrix Laboratory它的核心数据结构就是矩阵标量是1×1的矩阵向量是1×n或者n×1的矩阵矩阵就是m×n的完整数组。很多报错你之所以看不懂是因为脑子里还在把向量当成“一列数据”而MATLAB眼里只有“矩阵的尺寸”和“矩阵乘法的规则”。举个例子你在MATLAB里写x [1 2 3]它的形态是1行3列也就是行向量你写x [1; 2; 3]它的形态是3行1列是列向量。这两种形态在屏幕上看起来都是“一串数字”但在矩阵运算里的意义完全不同。判断一个变量的真实尺寸用size()函数就行了x_row [1 2 3]; x_col [1; 2; 3]; size(x_row) % 结果为 1 3 size(x_col) % 结果为 3 1我在带新人时经常做一个练习让他们先用size打印出脚本中每个变量的维度再解释为什么某个运算会报错。这个习惯非常有用尤其是当你从别人手里接过来一段代码时百分之八十的报错都可以靠打印维度排查掉。理解了“一切皆矩阵”你再回头看length和numel的区别就很容易了。length(x)返回的是最长那个维度的长度numel(x)返回的是元素总个数。对一个向量来说两者相等但一旦变量变成矩阵length就可能不再是你想要的那个数。1.2 行向量与列向量方向决定命运创建行向量和列向量的方式表面上只差一个分号或转置但在实际计算中这个方向会直接影响运算结果。行向量用空格或逗号分隔元素列向量用分号分隔比如a [1 2 3]; % 1行3列 b [1; 2; 3]; % 3行1列 c a; % 转置成列向量 d b.; % 转置成行向量这里有个非常重要的细节和.在实数向量上效果一样都是转置但在复数向量上它们完全不同。是共轭转置会把虚部符号翻转.是普通转置不会改变元素值。后面我会专门讲复向量的问题这里你先记住一个原则如果你只是想把行变列、列变行而不是做数学意义上的共轭那就统一用.这样无论未来处理什么数据都不会踩坑。行向量与列向量的“方向”还会影响隐式扩展和矩阵乘法。举个常见的例子如果你想把一组数据和另一个向量做点运算方向的差异会导致维度不匹配。我在实际项目中见过有人因为写漏了一个转置符号导致整个信号滤波结果被转置到相反方向调试了整整一个下午。所以每当你创建向量时心里要先问一句我到底要的是1×n还是n×11.3 复向量带来的共轭转置陷阱复向量在信号处理和通信系统里非常常见这时和.的差异就不是纸上谈兵了。看个具体例子z [12i, 3-4i, 56i]; z1 z; % 共轭转置元素变成 [1-2i; 34i; 5-6i] z2 z.; % 普通转置元素还是 [12i; 3-4i; 56i]只是方向变了如果是在做FFT频谱分析你拿到了复数频谱想把它从行向量转成列向量再拼到矩阵里不小心用了那么虚部符号就全变了后续的相位分析全部作废而且这种错误很难用肉眼看出来因为模值几乎没变化只有相位不对。很多老手也习惯写是因为早期MATLAB版本里人们常混用但从我自己的经验看绝不要在图省事的地方埋这种雷。2. 创建向量从冒号到函数批量生成2.1 冒号运算符最常用的等间隔向量创建向量最基础也最常用的方式就是冒号运算符。语法是起始值:步长:结束值步长省略时默认为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; % 空向量因为步长为正且起始值大于结束值这里需要提醒一个新手很容易忽略的点冒号的步长可以是负数但方向必须和起始到结束的方向一致。如果你想生成一个递减向量却忘了写负步长得到的不是报错而是一个空向量。空向量本身不报错但当你后续用x(1)去访问它的第一个元素时就会弹出索引越界的错误这种问题在真实代码里隐蔽性极高。另外浮点步长有一个潜在风险。像是0:0.1:1这种写法虽然大多数情况下能得到11个元素但由于浮点数无法精确表示0.1个别版本或边界条件下可能会出现最后一点不是1的情况。我个人的习惯是如果明确知道需要多少个等间隔点就用linspace如果只是想用步长生成一个粗糙的采样序列冒号运算符也没问题但不要在浮点步长的向量上做精确等式判断比如x(11) 1这种很容易得到逻辑假。2.2 linspace与logspace等分与对数间隔linspace用来生成指定起点和终点之间固定数量的等间隔点语法是linspace(起始, 终点, 点数)。点数不写时默认是100。它在画图时特别常用因为利用它可以非常精确地控制采样个数t linspace(0, 2*pi, 200); % 200个点从0到2*pi均匀分布 y sin(t); plot(t, y);你可能会问既然冒号运算符也能生成等间隔向量为什么要用linspace两个原因。第一linspace的点数是确定的你不用去算步长第二linspace内部会把首尾点都包含进去对于需要包含端点的场合比如设置坐标轴范围更加可靠。典型情况是你要画一个光滑的正弦曲线用冒号写0:0.01:2*pi大约生成629个点但终点因为浮点误差不一定精确落在2*pi上用linspace(0, 2*pi, 630)就能精确定位。logspace和linspace类似但它生成的是对数间隔的点常用于频率轴。logspace(-2, 2, 5)会生成从0.01到100之间按10的幂分布的5个点也就是0.01、0.1、1、10、100。在做伯德图、频响分析或者取采样频率的对数刻度的场景中logspace几乎是标配。2.3 zeros、ones、rand等函数批量生成除了手工列举元素更实用的是用函数一次性生成向量。下面这几个函数是我平时用得最多的a zeros(1, 5); % [0 0 0 0 0] b ones(3, 1); % [1; 1; 1] c rand(1, 10); % 10个[0,1)均匀随机数 d randn(1, 10); % 10个标准正态随机数 e randi([1, 100], 1, 8); % 8个1到100之间的随机整数需要特别说明的是randi的第三、第四个参数分别代表行数和列数千万别把顺序搞反。我见过有人想要10个1到100的随机整数结果写成了randi([1, 100], 10)得到的是10×10的矩阵而不是向量后续运算全部乱套。repmat和repelem也值得一提它们都能复制向量但复制逻辑不同。repmat(a, 1, 3)把整个向量当作原子来重复结果是[1 2 3 1 2 3 1 2 3]repelem(a, 2)则把每个元素分别重复结果是[1 1 2 2 3 3]。这两个函数在矩阵扩展和样本生成时非常实用但很多人到了一两年后才开始使用其实早期学会能省很多循环。3. 向量的三种核心运算点运算、点积叉积、范数归一化3.1 元素级运算与矩阵运算的区别这是MATLAB新人最大的分水岭要不要在运算符前面加那个点。*是矩阵乘法.*是元素级乘法/和./、^和.^同理。矩阵乘法要求内维度匹配而元素级运算要求两个向量形状完全一致或者满足隐式扩展条件。打个比方。矩阵乘法像“两个队伍按规则配对”行向量的每个元素要和列向量的每个元素组合求积所以1×3的行向量不能直接乘以1×3的行向量内维度不匹配。元素级运算像“两个人面对面结对手拉手”对应位置的元素逐一相乘所以只要两个向量尺寸一致就能算。看这段代码a [1 2 3]; b [4 5 6]; c a .* b; % [4 10 18]逐元素相乘 d a * b; % 32矩阵乘法内积 e a * b; % 报错Inner matrix dimensions must agree.我指导过很多学生他们第一次写向量内积时都习惯写成a * b然后被报错吓到。其实此时要么写成a * b要么用dot(a, b)。而如果你只是想对两个等长向量的每个对应位置做运算就老老实实加个点。判断标准很简单你关心的是“整体组合”还是“逐个对应”前者用矩阵运算后者用点运算。3.2 点积与叉积的工程含义MATLAB里直接用dot和cross函数不用自己写求和公式。点积也叫内积公式上等于sum(a .* b)几何意义是一个向量在另一个向量方向上的投影长度乘以另一个向量的长度。在数据分析里点积经常用来衡量两个序列的相关程度。计算两个传感器信号的点积如果值很大说明波形趋势相近如果接近零说明两者几乎正交。a [1, 2, 3]; b [4, 5, 6]; p dot(a, b); % 32 p2 sum(a .* b); % 同样为32叉积则不同cross(a, b)返回一个垂直于a和b所张成平面的新向量方向由右手定则决定。三维空间中叉积常用于求法向量、旋转轴。例如已知平面上两个不平行向量取它们的叉积就能得到该平面的法向量这在计算机图形学里非常常用。二维向量也能用cross它返回一个标量表示“有向面积”的正负可以用来判断三个点构成的角度是顺时针还是逆时针p1 [0, 0]; p2 [1, 0]; p3 [0, 1]; c cross([p2-p1], [p3-p1]); % 返回正数说明逆时针3.3 范数与归一化范数用来衡量向量的大小。常见的三种是1范数绝对值之和、2范数欧几里得长度、无穷范数最大绝对值。MATLAB里直接norm(v, 1)、norm(v, 2)或者norm(v)默认2范数、norm(v, Inf)即可。v [3, 4, 0]; n2 norm(v); % 5 n1 norm(v, 1); % 7 ninf norm(v, Inf); % 4归一化就是把向量按比例缩放到单位长度或特定范围常用两种方式。一种是让向量的2范数变为1即normalize(v, norm)另一种是把元素线性映射到[0, 1]区间即normalize(v, range)。这个操作在机器学习数据预处理里几乎必做因为如果不归一化量纲差异大的特征会直接压过其他特征比如一个范围是0到10000的变量和一个范围是0到1的变量放在一起算距离后者基本不起作用。v [10, 20, 30]; u normalize(v, norm); % 模长为1的向量 r normalize(v, range); % [0, 0.5, 1]这里有个细节容易被忽略normalize函数返回的结果在R2018a之后可用如果你的MATLAB版本比较老可以用手写方式代替比如v ./ norm(v)和(v - min(v)) ./ (max(v) - min(v))。效果完全一样。4. 索引与切片数据快速定位的多种技巧4.1 下标索引、end与步长MATLAB的索引从1开始这一点和Python的0起始完全不同初学阶段最容易出低级错误。基本用法有下面几种x [10, 20, 30, 40, 50]; x(2); % 20单元素索引 x(1:3); % [10 20 30]连续切片 x(2:2:end); % [20 40]带步长的切片 x(end); % 50最后一个元素 x(end:-1:1); % [50 40 30 20 10]倒序end是一个隐式的索引关键字它等价于当前维度的长度。用end的好处是即使向量长度变化代码依然自动适配。我自己的习惯是只要涉及数组末端位置一律用end而不是硬编码一个具体数字比如x(x中元素的个数)这种写法的维护成本太高一旦前面的处理改变了向量长度后边的索引就要跟着改。这里还要提醒一句MATLAB没有负数索引。x(-1)不会像Python那样返回倒数第一个元素而是直接报错“数组索引必须为正整数或逻辑值”。很多人从Python转过来都会在这上面浪费几分钟。4.2 逻辑索引一步筛出你想要的数据逻辑索引是MATLAB非常强大但新手不太适应的一种索引方式。它的核心思想是用一个与向量等长的逻辑数组由true和false组成作为索引只有对应位置为true的元素才会被取出。写起来非常直观data randn(1, 100); outliers data(abs(data) 2); % 直接取所有绝对值大于2的异常点 pos data(data 0); % 取所有正数 idx find(data 0); % 或者只取位置下标这里有两套索引体系位置下标索引比如data([1, 5, 7])和逻辑索引比如data(data 0)。逻辑索引的价值在于它把“条件判断”和“取数”合到了一步代码既短又不容易出错。如果你需要下标再用find函数转换。在实际项目里我用逻辑索引做数据清洗的比例非常高比如从传感器采集序列中剔除掉无效的NaN值valid data(~isnan(data) ~isinf(data));这一行就完成了传统写法里好几行的循环判断这就是向量化思维带来的效率。4.3 修改、插入与删除元素向量的修改和扩展很灵活但有几个隐藏行为必须知道。先看基本的x [1, 2, 3, 4]; x(2) 99; % 修改第二个元素[1 99 3 4] x(end 1) 100; % 末尾追加[1 99 3 4 100] x [x(1), 50, x(2:end)]; % 在第二个位置插入50 x(3) []; % 删除第三个元素重点说一下那个隐藏坑如果你直接给一个中间跳空的位置赋值MATLAB不会报错而是自动用0填充跳跃部分。比如x [10, 20, 30]; x(5) 99;得到的x是[10 20 30 0 99]。这个行为在很多情况下不是你想要的尤其是从循环里动态组装数据时一旦索引写错会悄无声息地多出一堆0后续算均值、找峰值全都会受影响。排查这种bug比报错要痛苦得多因为程序“正常运行”但结果错了。所以在动态扩展向量时要么用end 1的方式每次都追加到末尾要么干脆初始化一个足够长的零向量再按位置填充。5. 可视化与向量化编程5.1 plot、stem与直方图把向量画出来向量的可视化是验证算法正确性的最快方式。最基本的plot用法是横坐标向量加纵坐标向量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;需要注意的一点是plot的两个参数必须是同尺寸的向量横纵都行但如果一个是行向量另一个是列向量在旧版本里会得到一堆杂乱曲线而不是一条线。现在的新版本支持隐式扩展会自动调整但为了可读性还是建议画图前统一方向。对于离散数据stem比plot更合适要观察分布形态直接用histogramr randn(1, 10000); histogram(r, 50);这个图一看就能判断数据是否接近正态分布比你计算任何统计量都直观。我在做信号分析时几乎每次拿到一段新数据的第一件事就是画图先看形态再决定用什么算法处理。可视化不是为了发朋友圈是为了帮你确认数据的“脾气”。5.2 向量化思维用内置函数取代for循环向量化是MATLAB性能优化里最重要的一课。因为MATLAB本质是解释型语言纯循环的开销很大而内置函数背后是高度优化的C和Fortran代码还利用了现代CPU的并行能力。比如你要计算序列的累积和可以写循环x 1:100000; y zeros(size(x)); for k 1:numel(x) y(k) sum(x(1:k)); end但一行cumsum(x)就能做到同样的事速度可能快几十倍。类似的还有diff用来差分、abs取绝对值、sin和cos直接对整个向量操作、exp和log也能直接用。新建向量时不要写y[]然后循环里逐元素添加那是最慢的写法学会把“逐点计算”翻译成“对整个数组的一次函数调用”。我自己在带项目时做过一个简单的性能测试对100万个随机数分别用循环和向量化计算exp(x).*sin(x)的和向量化版本运行时间常常不到循环版的十分之一。性能差异在几百万点的数据上非常明显这在写实时处理脚本时是生死攸关的问题。不过也要说句公道话不是所有场景都适合向量化比如递归算法或者每个新值依赖前一个值的迭代过程硬要向量化只会把代码写得像天书这时候用循环反而清晰。6. 常见报错与排错实录6.1 维度不匹配先查size再做运算最常见的报错就是Error using *和Inner matrix dimensions must agree.。这条报错几乎可以翻译成一句话你把矩阵乘法用在了不该用的地方或者向量的方向不对。处理思路分三步第一用size打印出参与运算的各变量维度第二确认你到底想要矩阵乘法还是元素级运算第三如果是想得到标量内积但两个都是行向量记得用a * b或dot(a, b)。还有一类维度不匹配是拼接时产生的比如[a, b]要求a和b都是行向量且列数兼容如果一个是行向量一个是列向量就会报错或者产生一个意外形状的矩阵。我会在所有读入数据的脚本里加一行disp(size(data))确保从一开始就知道数据形态后边才不会出连环错。6.2 索引越界与空向量为什么x(1)也会报错报错文本Index exceeds the number of array elements.意味着你访问的下标大于当前向量长度。这种情况经常发生在循环里循环边界用了导致多走了一位或者冒号方向写反生成了空向量再去访问空向量的第一个元素。空向量在MATLAB里是合法的size([])结果是0×0但你访问x(1)就会报错。排查技巧是报错那一行之前先打印numel(x)和出问题的索引值。比如k 5; disp(numel(x)); disp(k); x(k);这样能立刻看出是索引超出了真实长度还是循环计数本身就错了。另一个容易被忽略的场景是find找不到任何满足条件的元素它返回空数组然后你用这个空数组去索引原向量同样会报错。我习惯在find之后先判断一下是否为空再决定后续操作。6.3 隐式扩展与预分配两个提升性能和稳定性的习惯新版MATLAB支持隐式扩展也就是允许一个向量和一个标量直接运算比如[1 2 3] 1会得到[2 3 4]不需要手动写ones(1,3)去扩展。这大大方便了日常计算但也有一些反面教训。当你写a [1 2 3]; b [4; 5; 6]; a b时隐式扩展会把a扩展成3×3、把b也扩展成3×3得到3×3矩阵。这个行为在旧版本里会直接报错现在却“正常执行”了。如果你没意识到这一点后续代码可能收到一个预期之外的矩阵。所以算完一个表达式后打印一下size仍然是排查问题的好习惯。预分配指的是在循环前先创建好目标向量比如n 10000; y zeros(1, n); for k 1:n y(k) k^2; end而不是在循环内部用y [y, k^2]不断让向量变长。动态增长会让MATLAB反复重新分配内存数据量一大性能直线下降。我在写仿真脚本比如随机游走模型或逐点信号的递推计算时都强制自己先zeros预分配这个习惯在数据量达到百万级别时能明显感觉到差异。还有一个相关的坑是直接给跳空位置赋值造成自动补0前面已经强调过这里不再重复。这篇笔记按我自己踩坑的顺序整理了一遍。最后分享一个我一直保留的小习惯每写完一段向量相关代码先用size打印一下关键变量的维度尤其是在转置、拼接、逻辑索引之后。维度对了后面基本不会出大乱子。初学的朋友可以自己做个小练习生成1000个随机数分别用循环和向量化方式计算exp(x).*sin(x)的和对比运行时间你会有非常直观的感受。向量是MATLAB一切计算的地基这关打牢之后再去学矩阵运算、机器学习算法或者图像处理都会顺畅很多。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表