Matlab 将函数应用于滚动窗口
假设我有一长串的Matlab 将函数应用于滚动窗口,matlab,vector,Matlab,Vector,假设我有一长串的a值(比如长度1000),我想计算100对std,即我想计算std(a(1:100)),std(a(2:101)),std(a(3:102)),std(a(901:1000)) 在Excel/VBA中,您可以通过在一个单元格中编写例如=STDEV(A1:A100)来轻松完成此任务,然后一次性填写。现在我的问题是,如何在Matlab中高效地实现这一点,而不必使用任何昂贵的for循环。 编辑:是否也可以对时间序列列表执行此操作,例如a的维度为1000 x 4(即长度为1000的4个时
a
值(比如长度1000),我想计算100对std
,即我想计算std(a(1:100))
,std(a(2:101))
,std(a(3:102))
,std(a(901:1000))
在Excel/VBA中,您可以通过在一个单元格中编写例如=STDEV(A1:A100)
来轻松完成此任务,然后一次性填写。现在我的问题是,如何在Matlab中高效地实现这一点,而不必使用任何昂贵的for循环。编辑:是否也可以对时间序列列表执行此操作,例如a的维度为1000 x 4(即长度为1000的4个时间序列)?然后,输出矩阵的维数应为901 x 4。您所做的基本上是一个过滤操作 如果您可以访问图像处理工具箱
stdfilt(A,ones(101,1)) %# assumes that data series are in columns
将实现这一技巧(无论
A
的维度如何)。请注意,如果您还可以访问并行计算工具箱,那么您可以让类似这些过滤操作在GPU上运行,尽管您的问题可能太小,无法产生明显的加速 注意:有关最快的解决方案,请参见
因此,首先使用for循环来实现这一点(特别是如果这些是您的实际维度的话)并不昂贵。除非您使用的是非常旧的Matlab版本,否则JIT编译器(当然还有预分配)使循环变得便宜
第二,你试过循环吗?因为在过早开始优化之前,您应该首先尝试天真的实现
第三-arrayfun
可以使它成为一个单行程序,但它基本上只是一个for循环,有额外的开销,如果你真的关心速度的话,它很可能比for循环慢
最后是一些代码:
n = 1000;
A = rand(n,1);
l = 100;
对于回路(几乎不笨重,可能是高效的):
矢量化(高效内存!)解决方案:
[X,Y] = meshgrid(1:l)
S = std(A(X+Y-1))
一个可能更好的矢量化解决方案(和一条直线),但仍具有高效的内存:
S = zeros(n-l+1,1); %//Pre-allocation of memory like this is essential for efficiency!
for t = 1:(n-l+1)
S(t) = std(A(t:(t+l-1)));
end
S = std(A(bsxfun(@plus, 0:l-1, (1:l)')))
请注意,使用所有这些方法,您可以用任何函数替换std
,只要它适用于矩阵的列(这是Matlab中的标准)
进入2D: 要进入2D,我们需要进入3D
n = 1000;
k = 4;
A = rand(n,k);
l = 100;
ind = bsxfun(@plus, permute(o:n:(k-1)*n, [3,1,2]), bsxfun(@plus, 0:l-1, (1:l)')); %'
S = squeeze(std(A(ind)));
M = squeeze(mean(A(ind)));
%// etc...
或
或
或者(摘自-注意,在他的回答中,他展示了一个比这个简单循环更快的选择)
在Matlab中有几个函数可以有效地完成这项工作 一方面,您可以使用诸如
colfilt
或nlfilter
之类的函数,这些函数在滑动块上执行计算colfilt
比nlfilter
更有效,但仅当块内元素的顺序无关紧要时才能使用。以下是如何在您的数据上使用它:
S = colfilt(A, [100,1], 'sliding', @std);
或
在您的示例中,您可以清楚地看到性能的差异。但有一个技巧:两个函数都填充输入数组,以便输出向量与输入数组具有相同的大小。要仅获取输出向量的相关部分,需要跳过第一个floor((100-1)/2)=49
第一个元素,并获取1000-100+1
值
S(50:end-50)
但还有另一种解决方案,接近于colfilt
,效率更高colfilt
调用col2im
将输入向量重塑为一个矩阵,它在该矩阵上对每个不同的列应用给定的函数。这会将大小为[1000,1]的输入向量转换为大小为[100901]的矩阵。但是colfilt
用0或1填充输入数组,您不需要它。因此,您可以在不使用填充步骤的情况下运行colfilt
,然后对每列应用std
,这很容易,因为对矩阵应用std
会返回列的std的行向量。最后,如果需要,将其转置以获得列向量。简而言之,一行:
S = std(im2col(X,[100 1],'sliding')).';
S = foldfunction( @std , A , l ) ;
备注:如果您想应用更复杂的函数,请参阅
colfilt
的代码,第144行和第147行(对于v2013b)。如果您关心的是for
循环的速度,您可以通过将向量折叠成数组(使用重塑
)来大大减少循环迭代次数列具有要应用函数的元素数
这将使Matlab和JIT通过计算数组中每列的函数来执行优化(在大多数情况下,他们比我们做得更好)
然后重塑阵列的偏移版本,并执行相同的操作。您仍然需要一个循环,但迭代次数将仅为l
(在您的示例中为100
),而不是n-l+1=901
的经典for
循环(一次一个窗口)。
完成后,在向量中重塑结果数组,然后仍然需要手动计算最后一个窗口,但总体而言,速度要快得多
采用与Dan相同的输入符号:
n = 1000;
A = rand(n,1);
l = 100;
它将采取这种形式:
width = (n/l)-1 ; %// width of each line in the temporary result array
tmp = zeros( l , width ) ; %// preallocation never hurts
for k = 1:l
tmp(k,:) = std( reshape( A(k:end-l+k-1) , l , [] ) ) ; %// calculate your stat on the array (reshaped vector)
end
S2 = [tmp(:) ; std( A(end-l+1:end) ) ] ; %// "unfold" your results then add the last window calculation
如果我tic。。。toc
完整循环版本和折叠版本,我得到了以下平均结果:
Elapsed time is 0.057190 seconds. %// windows by window FOR loop
Elapsed time is 0.016345 seconds. %// "Folded" FOR loop
我知道tic/toc
不是实现完美计时的方法,但我的matlab版本中没有timeit
功能。此外,差异非常显著,足以表明存在改进(尽管这种方法无法精确量化)。当然,我删除了第一次运行,并检查结果是否与不同的矩阵大小一致
关于您的“一行程序”请求,我建议您将此代码包装成如下函数:
function out = foldfunction( func , vec , nPts )
n = length( vec ) ;
width = (n/nPts)-1 ;
tmp = zeros( nPts , width ) ;
for k = 1:nPts
tmp(k,:) = func( reshape( vec(k:end-nPts+k-1) , nPts , [] ) ) ;
end
out = [tmp(:) ; func( vec(end-nPts+1:end) ) ] ;
主代码中的哪一个允许您在一行中调用它:
S = std(im2col(X,[100 1],'sliding')).';
S = foldfunction( @std , A , l ) ;
这种格式的另一个好处是,您可以对其他统计函数使用非常相同的子函数。例如,如果您想要窗口的“平均值”,只需更改func
参数即可调用相同的值:
S = foldfunction( @mean , A , l ) ;
唯一的限制,因为它是
S = foldfunction( @std , A , l ) ;
S = foldfunction( @mean , A , l ) ;
A = randn(1000,4); %// random data
N = 100; %// window size
c = size(A,2);
A1 = [zeros(1,c); cumsum(A)];
A2 = [zeros(1,c); cumsum(A.^2)];
S = sqrt( (A2(1+N:end,:)-A2(1:end-N,:) ...
- (A1(1+N:end,:)-A1(1:end-N,:)).^2/N) / (N-1) ); %// result
%// File loop_approach.m
function S = loop_approach(A,N);
[n, p] = size(A);
S = zeros(n-N+1,p);
for k = 1:(n-N+1)
S(k,:) = std(A(k:(k+N-1),:));
end
%// File bsxfun_approach.m
function S = bsxfun_approach(A,N);
[n, p] = size(A);
ind = bsxfun(@plus, permute(0:n:(p-1)*n, [3,1,2]), bsxfun(@plus, 0:n-N, (1:N).')); %'
S = squeeze(std(A(ind)));
%// File cumsum_approach.m
function S = cumsum_approach(A,N);
c = size(A,2);
A1 = [zeros(1,c); cumsum(A)];
A2 = [zeros(1,c); cumsum(A.^2)];
S = sqrt( (A2(1+N:end,:)-A2(1:end-N,:) ...
- (A1(1+N:end,:)-A1(1:end-N,:)).^2/N) / (N-1) );
%// Benchmarking code
clear all
A = randn(1000,4); %// Or A = randn(1000,1);
N = 100;
t_loop = timeit(@() loop_approach(A,N));
t_bsxfun = timeit(@() bsxfun_approach(A,N));
t_cumsum = timeit(@() cumsum_approach(A,N));
disp(' ')
disp(['loop approach: ' num2str(t_loop)])
disp(['bsxfun approach: ' num2str(t_bsxfun)])
disp(['cumsum approach: ' num2str(t_cumsum)])
disp(' ')
disp(['bsxfun/loop gain factor: ' num2str(t_loop/t_bsxfun)])
disp(['cumsum/loop gain factor: ' num2str(t_loop/t_cumsum)])
loop approach: 0.092035
bsxfun approach: 0.023535
cumsum approach: 0.0002338
bsxfun/loop gain factor: 3.9106
cumsum/loop gain factor: 393.6526
loop approach: 0.085618
bsxfun approach: 0.0040495
cumsum approach: 8.3642e-05
bsxfun/loop gain factor: 21.1431
cumsum/loop gain factor: 1023.6236