洛伦谈MATLAB的艺术

将想法转化为MATLAB

MATLAB算法在R2016b中的扩展

我很荣幸地介绍今天的客座博主,我的同事,史蒂夫·埃丁斯. 他在我们的工具中大量参与了图像处理功能,最近还为设计MATLAB语言的附加和改进做出了重大贡献。

今年夏天早些时候,我在写一些颜色空间转换代码。在代码中,我有一个Px3矩阵叫做RGB,其中包含P个颜色,每行一个。我还有一个1x3向量,v. 我需要把每一列数据相乘RGBv,例如:

RGB_c = [RGB (: 1) * v (1) RGB (:, 2) * v (2) RGB (:, 3) * v (3)];

但是因为我使用的是MatlabR2016的内部开发版本(9月14日发布),所以我没有键入上面的代码。相反,我键入了以下内容:

RGB_c=RGB.*v;

在R2016a和更老的MATLAB版本中,这行代码产生了一个错误:

>> RGB_c = RGB .* v错误使用.*矩阵维数必须一致。

在新的版本中,MATLAB隐式地扩大向量v与矩阵大小相同RGB然后执行元素乘法,我说“隐式”,因为MATLAB实际上并没有在内存中复制扩展向量。

今天我想解释一下MATLAB算术运算符(和一些函数)的这种新的隐式扩展行为。我将讨论它是如何工作的,以及我们为什么这样做。在讨论的下一部分中,我将使用MathWorks的几乎所有人在讨论这个主题时都使用的一个示例:从矩阵中减去列的平均值。

目录

减去列的意思是:十年一个十年

假设你有一个矩阵A..

兰德(3、3)=
A=0.48976 0.70936 0.6797 0.44559 0.75469 0.6551 0.64631 0.27603 0.16261

假设你想修改的每一列A.通过减去列的平均值。这个意思是函数方便地为您提供了每个列的含义:

ma=平均值(A)
ma=0.527220.58003 0.49914

但是自从文科硕士是不是和A.而不是标量,你不能直接减法文科硕士A.直接地相反,你必须扩张文科硕士大小与A.然后做减法。

在MATLAB的前十年,专家用户通常使用一种索引技术称为托尼的把戏进行扩展。

ma_=ma(一(3,1),:)
扩展后的ma_=0.527220.58003 0.49914 0.527220.58003 0.49914 0.527220.58003 0.49914
A - ma_expanded
ans=-0.037457 0.12934 0.18057-0.081635 0.17466 0.15596 0.11909-0.304-0.33653

在MATLAB的第二个十年(粗略地说),大多数人开始使用一个名为雷普马特(“复制矩阵”的缩写)来做扩展。

ma_扩展=repmat(ma,3,1)
Ma_expansion = 0.52722 0.58003 0.49914 0.52722 0.58003 0.49914 0.52722 0.58003 0.49914

使用函数雷普马特比使用Tony的技巧更具可读性,但它仍然在内存中创建了扩展矩阵。对于非常大的问题,额外的内存分配和内存复制可能会显著降低计算速度,甚至导致内存不足错误。

在MATLAB的第三个十年里,我们引入了一个新函数bsxfun这样就可以直接做减法运算,而不用在内存中生成一个展开的向量。你是这样说的:

bsxfun(@负,A,ma)
ans=-0.037457 0.12934 0.18057-0.081635 0.17466 0.15596 0.11909-0.304-0.33653

函数名中的“bsx”指的是“二进制单例扩展”,这里的术语“二进制”指的是接受两个输入的运算符。(不,这不是任何人最喜欢的函数名。)

这个函数可以工作,并且已经被使用了相当多。一年前,大约有1800种bsxfun在740个文件中。

但也有人抱怨bsxfun.

疼痛

除了这个尴尬的名字,还有其他可用性和性能问题bsxfun.

  • 没有多少人知道这个功能。一个人在寻求减法帮助时是否应该去寻找它,这一点都不明显。
  • 使用bsxfun需要某种程度的编程抽象(调用一个函数将另一个函数应用到一组输入),这似乎与应用程序不匹配(基本算术)。
  • 使用bsxfun需要相对先进的MATLAB编程知识。你必须理解函数句柄,并且必须了解MATLAB算术运算符的函数等价物(例如,,时代rdivide).
  • 这对学生来说更难MATLAB执行引擎生成器生成的代码与用于基本算术的代码一样高效。
  • 使用bsxfun看起来不像数学。(有些人甚至称之为丑陋。)

于是,14年后克里夫·莫勒尔最初提出这样做的时候,我们已经改变了MATLAB的两个输入算术运算符、逻辑运算符、关系运算符和几个两个输入函数来做bsxfun-只要输入已更改,样式隐式展开就会自动进行兼容的大小.

兼容的大小

表情A - B只要A.B兼容的大小。对于每个维度,如果输入的维度大小相同或其中一个为1,则两个数组具有兼容的大小。在最简单的情况下,如果两个数组大小完全相同或其中一个为标量,则两个数组大小是兼容的。

这里有一些不同箱子兼容尺寸的插图。

  • 两个输入的大小完全相同。

  • 一个输入是标量。

  • 一个输入是矩阵,另一个是具有相同行数的列向量。

  • 一个输入是列向量,另一个是行向量。请注意,在这种情况下,两个输入都是隐式扩展的,每个输入的方向不同。

  • 一个输入是矩阵,另一个输入是具有相同行数和列数的三维数组。请注意,矩阵的大小A.在第三维中被隐式地认为是1A.可以在第三维中展开,使其大小与B.

  • 一个输入是矩阵,另一个是三维阵列。所有尺寸要么相同,要么其中一个为1。请注意,这是另一种情况,其中两个输入都隐式展开。

万博1manbetx支持的运算符和函数

这是MATLAB运算符和函数的初始集合,它们现在具有隐式展开行为。

+-.*./.\.^<=>===~=|&xor位或位和位异或最小最大模rem形下压atan2

我预计随着时间的推移,其他函数将被添加到这个集合中。

反对

MathWorks对MATLAB算法的这种改变并非没有争议。有些人担心用户可能编写了某种程度上依赖于这些操作符的代码,在某些情况下会产生错误。但是,在检查了我们自己的代码库之后,在预览了R2016a和R2016b预发行版中的更改之后,我们没有看到在实践中出现重大的兼容性问题。

另一些人认为新的算子行为并不充分地基于线性代数符号。然而,与其将MATLAB看作一个纯粹的线性代数符号,不如将MATLAB看作一个矩阵和数组计算符号。从这个意义上说,MATLAB在发明被广泛接受的符号方面有着悠久的历史,包括反斜杠、冒号和各种形式的下标。

最后,一些人担心当用户试图添加两个向量时会发生什么,而没有意识到一个是列,另一个是行。在早期版本的MATLAB中,这会产生错误。在R2016b中,它生成一个矩阵。(我把这个矩阵称为外和但我们相信这个问题会立即被注意到并很容易得到纠正。事实上,我认为比起错误地使用*运算符,而不是.*此外,MATLAB中相对较新的数组大小保护限制(Preferences->MATLAB->Workspace->MATLAB array size limit)阻止MATLAB尝试形成可能导致内存不足的超大矩阵。

实践中的隐性扩张

在我们决定做出这一改变之前,我们对人们的使用情况进行了研究bsxfun.在结束这篇文章之前,我将向你展示bsxfun就像你在R2016b中用隐式展开重写它们一样。

将遮罩应用于truecolor图像。

% rgb: 480x640x3
%旧rgb2=bsxfun(@次,rgb,掩码);
%新的rgb2=rgb.*掩码;

标准化矩阵列(减去平均值,除以偏差)。

%X:1000x4μ=平均值(X);σ=标准(X);
%旧的Y=bsxfun(@rdivide,bsxfun(@减号,X,mu),sigma);
% NEW Y = (X - mu) ./;

计算成对距离矩阵。

对于两组向量,计算每个向量对之间的欧氏距离。

%X:4x2(4个向量)%Y:3x2(3个向量)X=重塑(X,[4 1 2]);Y=重塑(Y[1 3 2]);
% OLD m = bsxfun(@minus,X,Y);D =函数(m (:,: 1), m (:,: 2));
%新m=X-Y;D=hypot(m(:,:,1),m(:,:,2));

计算外和。

此示例来自托普利兹函数。看到我关于邻居索引的文章另一个应用程序。

cidx = (0: m - 1);ridx = p: 1:1;
%旧ij=bsxfun(@plus,cidx,ridx);
%新ij=cidx+ridx;

查找相互为倍数的整数。

此示例来自于中Redheffer矩阵的计算画廊函数。它说明了与运算符不同的函数的隐式展开行为。

i=1:n;
%旧A=bsxfun(@rem,i,i')==0;
% NEW A = rem(i,i') == 0;

你自找的

最后,我想向多年来一直要求这种行为的所有MATLAB用户大喊一声当他将此评论包含在他的计划实施中时,代表了你们所有人提交:

%我们需要在任何地方进行完整的单例扩展。为什么%%[12]+[01]'==[12;23]?%bsxfun()完全是一种黑客行为,污染了%每个人的代码。

尤瓦尔,这是给你的。

读者们,你们用过吗bsxfun以前?你会利用新的算术行为吗?让我们知道在这里.




与MATLAB®R2016b一起发布

|

评论

要留下评论,请点击在这里登录到您的MathWorks帐户或创建一个新帐户。