我在学习一门机器学习课程时,提供了一个standardize
函数,但文档说明不足,而我对MATLAB还不太熟悉,所以我试图逐行分析这个函数。任何关于语法或标准化一般概念的解释都会对我有很大帮助。我们使用这个函数来标准化一个大型矩阵中提供的训练数据集。如果能详细解释代码片段中的大部分行,对我将非常有帮助。非常感谢。
function [X, mean_X, std_X] = standardize(varargin)switch nargin case 1 mean_X = mean(varargin{1}); std_X = std(varargin{1}); X = varargin{1} - repmat(mean_X, [size(varargin{1}, 1) 1]); for i = 1:size(X, 2) X(:, i) = X(:, i) / std(X(:, i)); end case 3 mean_X = varargin{2}; std_X = varargin{3}; X = varargin{1} - repmat(mean_X, [size(varargin{1}, 1) 1]); for i = 1:size(X, 2) X(:, i) = X(:, i) / std_X(:, i); end end
回答:
这段代码接受一个大小为M x N
的数据矩阵,其中M
是矩阵中一个数据样本的维度,N
是样本总数。因此,矩阵的一列就是一个数据样本。数据样本都是水平堆叠的,每一列都是一个样本。
现在,这段代码的真正目的是获取矩阵的所有列,并对数据进行标准化/归一化处理,使每个数据样本具有零均值和单位方差。这意味着经过这种转换后,如果你计算任何一列的均值,它将为0,方差将为1。这是统计分析、机器学习和计算机视觉中标准化值的标准方法。
这实际上源自统计分析中的z-score。具体来说,归一化的方程是:
给定一组数据点,我们用这些数据点的均值减去所需的值,然后除以相应的标准差。调用这段代码的方式如下。假设我们有一个矩阵,称之为X
,有两种调用方式:
- 方法#1:
[X, mean_X, std_X] = standardize(X);
- 方法#2:
[X, mean_X, std_X] = standardize(X, mu, sigma);
第一种方法会自动推断X
的每一列的均值和标准差。mean_X
和std_X
都会返回1 x N
的向量,提供矩阵X
中每一列的均值和标准差。第二种方法允许你手动指定X
的每一列的均值(mu
)和标准差(sigma
)。这可能用于调试,但在这种情况下,你需要将mu
和sigma
指定为1 x N
的向量。mean_X
和std_X
的返回值与mu
和sigma
相同。
在我看来,这段代码写得有点不好,因为你完全可以用向量化方式实现,但代码的核心是,如果我们使用方法#1,它会找到矩阵X
的每一列的均值,复制这个向量使其成为M x N
的矩阵,然后用X
减去这个矩阵。这将使每一列减去其各自的均值。我们还在均值减法之前计算了每一列的标准差。
一旦我们这样做了,我们接着通过将每一列除以其各自的标准差来归一化我们的X
。顺便说一下,执行std_X(:, i)
是多余的,因为std_X
已经是一个1 x N
的向量。std_X(:, i)
意味着获取第i
列的所有行。如果我们已经有一个1 x N
的向量,这可以简单地替换为std_X(i)
– 对我来说有点过分了。
方法#2与方法#1执行相同的操作,但我们为X
的每一列提供了自己的均值和标准差。
为了文档的目的,这是我对代码的注释方式:
function [X, mean_X, std_X] = standardize(varargin)switch nargin %// 检查输入到函数中的变量数量 case 1 %// 如果只有一个变量 - 这是输入矩阵 mean_X = mean(varargin{1}); %// 查找每一列的均值 std_X = std(varargin{1}); %// 查找每一列的标准差 %// 取X的每一列并减去其对应的均值 %// 取mean_X并在垂直方向上复制M次 X = varargin{1} - repmat(mean_X, [size(varargin{1}, 1) 1]); %// 接下来,对每一列按其各自的标准差进行归一化 for i = 1:size(X, 2) X(:, i) = X(:, i) / std(X(:, i)); end case 3 %// 如果我们提供了三个输入 mean_X = varargin{2}; %// 第二个输入是均值向量 std_X = varargin{3}; %// 第三个输入是标准差向量 %// 应用第一种情况中看到的代码 X = varargin{1} - repmat(mean_X, [size(varargin{1}, 1) 1]); for i = 1:size(X, 2) X(:, i) = X(:, i) / std_X(:, i); end end
如果我可以建议另一种编写这段代码的方式,我会使用强大而有力的bsxfun
函数。这避免了任何元素的复制,我们可以在幕后完成。我会重写这个函数,使其看起来像这样:
function [X, mean_X, std_X] = standardize(varargin)switch nargin case 1 mean_X = mean(varargin{1}); %// 查找每一列的均值 std_X = std(varargin{1}); %// 查找每一列的标准差 X = bsxfun(@minus, varargin{1}, mean_X); %// 用各自的均值减去每一列 X = bsxfun(@rdivide, X, std_X); %// 取每一列并除以其各自的标准差 case 3 mean_X = varargin{2}; std_X = varargin{3}; %// 与上面的代码相同 X = bsxfun(@minus, varargin{1}, mean_X); X = bsxfun(@rdivide, X, std_X);end
我认为上面的新代码比使用for
和repmat
要快得多。事实上,已知bsxfun
比前一种方法更快 – 特别是对于较大的矩阵。