У меня есть матрица:
S = [ -1.0400 4.9100 4.1000 -3.5450 -0.6600 -0.9300 4.3950 -1.0650 2.9850 -4.9800 0.2100;
-0.5200 -4.3150 -3.0950 0.5700 4.4700 1.1500 3.1350 0.6450 0.3750 -4.9150 -2.1150;
5.0000 5.0000 5.0000 5.0000 5.0000 5.0000 5.0000 5.0000 5.0000 5.0000 5.0000 ];
Я хочу преобразовать столбцы в единичные векторы, поэтому использую цикл for
for i=1:size(S,2)
S(:,i) = S(:,i) / norm( S(:,i) );
end
Есть ли способ сделать это более эффективно в MATLAB?





TL; DR
Если у вас есть MATLAB 2016b или новее и нет проблем с совместимостью, я бы использовал
S = S ./ sqrt(sum(S.^2,1));
Обновлено: см. Тест внизу для оценки производительности альтернатив.
Контекст
Мы можем просто вручную вычислить норму и разделить по столбцам.
По определению norm(x) = sqrt( sum( x(:).^2 ) ). Я использовал здесь (:), чтобы показать, что norm рассчитывается по всей матрице. Для нас полезно то, что sum по умолчанию работает по столбцам, поэтому норма по столбцам определяется следующим образом:
nrm = sqrt( sum( x.^2 ) );
Обратите внимание: если существует вероятность того, что ваша матрица S будет иметь только 1 строку, вам следует явно принудительно применить суммирование по столбцам с помощью nrm = sqrt(sum(x.^2,1)).
Теперь у нас есть несколько вариантов разделения:
Неявное расширение (MATLAB R2016b или новее)
S = S ./ nrm;
Неявное расширение с использованием bsxfun (все версии MATLAB)
S = bsxfun( @mrdivide, S, nrm );
Ручное расширение с использованием repmat (все версии MATLAB)
S = S ./ repmat(nrm, size(S,1), 1);
Если у вас есть MATLAB R2017b или новее, и снова нет проблем с совместимостью, вы можете использовать vecnorm, который можно использовать вместо ручного расчета нормы.
S = S ./ vecnorm(S, 2, 1);
Контрольный показатель:
Поскольку вы просили производительность, вот простой тест для проверки скорости этих различных методов. В частности, исходный цикл в вашем вопросе по сравнению с неявным расширением с помощью vecnorm или ручного расчета.
Результаты (запуск с использованием R2017b)
size(S): 1e3*1e2 1e5*1e3 1e3*1e6
Looping: 0.0005 1.0186 12.7788
Implicit manual: 0.0001 1.1236 10.4031
Implicit vecnorm: 0.0002 0.5774 6.8058
Выводы
vecnorm примерно в два раза быстрее других методов для больших матриц.1e5*1e3 зацикливание сравнимо с неявным расширением.Код
function benchie()
S = rand( 1e3, 1e2 )*5;
f1 = @() loopingNorm(S);
f2 = @() implicitManual(S);
f3 = @() implicitVecnorm(S);
fprintf( 'Looping: %.4f\nImplicit manual: %.4f\nImplicit vecnorm: %.4f\n', ...
timeit(f1), timeit(f2), timeit(f3) );
end
function S = loopingNorm(S)
for ii = 1:size(S,2)
S(:,ii) = S(:,ii) / norm( S(:,ii) );
end
end
function S = implicitManual(S)
S = S ./ sqrt(sum(S.^2,1));
end
function S = implicitVecnorm(S)
S = S ./ vecnorm( S, 2, 1 );
end
В 2018a и более поздних версиях также есть функция normalize, которая (с выбранным методом «norm») выглядит так, будто выполняет всю работу. Но это еще менее обратная совместимость ...
@etmuse Сейчас август 2018 года, все уже должны были заплатить за обновление своих лицензий, верно ?? Спасибо за информацию, похоже, синтаксис будет S = normalize(S, 1, 'norm'), хотя я не могу проверить.
@etmuse Следует отметить, что normalize использует vecnorm под капотом (это открытый исходный код). Кроме того, он автоматически обрабатывает NaN как нули, что в некоторых случаях может быть нежелательным побочным эффектом. Судя по количеству логики в этой функции, я бы не сказал, что это самая эффективная альтернатива ... Тем не менее, правильное предложение.
Возможно, стоит упомянуть, какая версия MATLAB использовалась для теста, увидев, как цикл for и другие оптимизации были введены в различных версиях (другими словами, результаты в некоторой степени зависят от локальной версии MATLAB).
@ Dev-iL Справедливо, я всегда обычно включаю версию в результаты тестов, забыл, но добавил сейчас :)
Пожалуйста, включите самую раннюю соответствующую версию MATLAB в свой вопрос, поскольку некоторые функции, полезные для вашей проблемы, были представлены только в последних выпусках.