Заставляя столбцы матрицы в разных пределах
У меня есть матрица с именем l размером 20X3. Я хотел сделать следующее: предположим, у меня есть следующие ограничения:
l1_max=20; l1_min=0.5;
l2_max=20; l2_min=0.5;
mu_max=20; mu_min=0.5;
Я хотел заставить все элементы матрицы l в определенных пределах. Значения 1-го столбца в пределах l1_max & l1_min. Значения 2-го столбца в пределах l2_max & l2_min. Значения 3-го столбца в пределах mu_max и mu_min.
То, что я сделал, было так:
for k=1:20
if l(k,1)>l1_max
l(k,1) = l1_max;
elseif l(k,1)<l1_min
l(k,1) = l1_min;
end
if l(k,2)>l2_max
l(k,2) = l2_max;
elseif l(k,2)<l2_min
l(k,2) = l2_min;
end
if l(k,3)>mu_max
l(k,3) = mu_max;
elseif l(k,3)<mu_min
l(k,3) = mu_min;
end
end
Можно ли сделать это лучше?
3 ответа
Вам не нужно перебирать строки, используйте векторизованные операции над целыми столбцами:
l(l(:, 1) > l1_max, 1) = l1_max;
l(l(:, 1) < l1_min, 1) = l1_min;
, подобным образом:
l(l(:, 2) > l2_max, 2) = l2_max;
l(l(:, 2) < l2_min, 2) = l2_min;
l(l(:, 3) > l2_max, 3) = mu_max;
l(l(:, 3) < l2_min, 3) = mu_min;
Альтернативный метод, который напоминает идею Баса, заключается в применении min
а также max
следующее:
l(:, 1) = max(min(l(:, 1), l1_max), l1_min);
l(:, 2) = max(min(l(:, 2), l2_max), l2_min);
l(:, 3) = max(min(l(:, 3), mu_max), mu_min);
Похоже, что оба подхода имеют сопоставимую производительность.
Вам даже не нужно перебирать все столбцы, операцию над всей матрицей можно выполнить за 2 вызова bsxfun
независимо от количества столбцов:
column_max = [l1_max, l2_max, mu_max];
column_min = [l1_min, l2_min, mu_min];
M = bsxfun(@min, M, column_max); %clip to maximum
M = bsxfun(@max, M, column_min); %clip to minimum
Это использует два трюка: чтобы обрезать значение между min_val и max_val, вы можете сделать clipped_x = min(max(x, min_val), max_val)
, Другой трюк состоит в том, чтобы использовать несколько непонятный bsxfun
, которая применяет функцию после выполнения одноэлементного расширения. Когда вы используете его на двух матрицах, перед применением функции он "выдавливает" наименьший размер, равный наибольшему, поэтому приведенный выше пример эквивалентен M = min(M, repmat(column_max, size(M, 1), 1))
, но, надеюсь, рассчитывается более эффективным способом.
Ниже приведен тест для тестирования различных методов, которые обсуждались до сих пор. Я использую функцию TIMEIT, найденную на файловой бирже.
function [t,v] = testClampColumns()
% data and limits ranges for each column
r = 10000; c = 500;
M = randn(r,c);
mn = -1.1 * ones(1,c);
mx = +1.1 * ones(1,c);
% functions
f = { ...
@() clamp1(M,mn,mx) ;
@() clamp2(M,mn,mx) ;
@() clamp3(M,mn,mx) ;
@() clamp4(M,mn,mx) ;
@() clamp5(M,mn,mx) ;
};
% timeit and check results
t = cellfun(@timeit, f, 'UniformOutput',true);
v = cellfun(@feval, f, 'UniformOutput',false);
assert(isequal(v{:}))
end
Даны следующие реализации:
1) Зациклите все значения и сравните их с минимальным / максимальным
function M = clamp1(M, mn, mx)
for j=1:size(M,2)
for i=1:size(M,1)
if M(i,j) > mx(j)
M(i,j) = mx(j);
elseif M(i,j) < mn(j)
M(i,j) = mn(j);
end
end
end
end
2) сравнить каждый столбец с мин / макс
function M = clamp2(M, mn, mx)
for j=1:size(M,2)
M(M(:,j) < mn(j), j) = mn(j);
M(M(:,j) > mx(j), j) = mx(j);
end
end
3) обрезать каждый столбец до предела
function M = clamp3(M, mn, mx)
for j=1:size(M,2)
M(:,j) = min(max(M(:,j), mn(j)), mx(j));
end
end
4) векторизованная версия усечения в (3)
function M = clamp4(M, mn, mx)
M = bsxfun(@min, bsxfun(@max, M, mn), mx);
end
5) сравнение абсолютных значений: -a |x| <а
(Примечание: это не применимо к вашему случаю, так как требует симметричного диапазона пределов. Я включил это только для полноты. Кроме того, это оказывается самый медленный метод.)
function M = clamp5(M, mn, mx)
assert(isequal(-mn,mx), 'Only works when -mn==mx')
idx = bsxfun(@gt, abs(M), mx);
v = bsxfun(@times, sign(M), mx);
M(idx) = v(idx);
end
Время, которое я получаю на моей машине с входной матрицей размером 10000x500:
>> t = testClampColumns
t =
0.2424
0.1267
0.0569
0.0409
0.2868
Я бы сказал, что все вышеперечисленные методы достаточно быстрые, с bsxfun
решение является самым быстрым:)