벡터화
벡터화 사용
MATLAB®은 행렬 및 벡터 연산에 최적화되어 있습니다. MATLAB 행렬과 벡터 연산을 사용하도록 루프 기반의, 스칼라 위주의 코드를 수정하는 과정을 벡터화라고 합니다. 다음과 같은 이유에서 코드를 벡터화할 가치가 있습니다.
모양: 벡터화된 수학 코드는 교재에서 볼 수 있는 수학 표현식과 비슷한 모양으로, 이해하기가 더 쉽습니다.
오류 발생 가능성 감소: 루프가 없는, 벡터화된 코드는 길이가 짧은 경우가 많습니다. 코드의 라인 수가 적으면 프로그래밍 오류가 발생할 가능성이 줄어듭니다.
성능: 벡터화된 코드는 같은 작업을 수행하는 루프가 포함된 코드보다 훨씬 더 빠르게 실행됩니다.
일반 계산에 대해 코드 벡터화하기
다음 코드는 0부터 10까지의 범위에 속하는 1,001개 값의 사인값을 계산합니다.
i = 0; for t = 0:.01:10 i = i + 1; y(i) = sin(t); end
다음은 동일한 코드가 벡터화된 형태입니다.
t = 0:.01:10; y = sin(t);
두 번째 코드 예제는 일반적으로 첫 번째 코드 예제보다 더 빠르게 실행되며, MATLAB을 보다 효율적으로 사용하는 방법입니다. 위의 코드가 포함된 스크립트를 만들어 시스템에서 실행 속도를 테스트하고, tic
과 toc
함수를 사용하여 실행 시간을 측정해 보십시오.
특정 작업에 대해 코드 벡터화하기
다음 코드는 매 5번째 요소에서 벡터의 누적합을 계산합니다.
x = 1:10000; ylength = (length(x) - mod(length(x),5))/5; y(1:ylength) = 0; for n= 5:5:length(x) y(n/5) = sum(x(1:n)); end
벡터화를 하면 훨씬 더 간결한 MATLAB 프로세스를 작성할 수 있습니다. 다음 코드는 그것의 한 예를 보여줍니다.
x = 1:10000; xsums = cumsum(x); y = xsums(5:5:length(x));
배열 연산
배열 연산자는 데이터 세트의 모든 요소에 대해 동일한 연산을 수행합니다. 이러한 연산 유형은 반복적인 계산에 유용합니다. 예를 들어, 원뿔의 지름(D
)과 높이(H
)를 기록하여 다양한 원뿔의 부피(V
)를 수집한다고 가정하겠습니다. 원뿔 하나에 대한 정보만 수집하는 경우 이 단일 원뿔의 부피는 다음과 같이 계산할 수 있습니다.
V = 1/12*pi*(D^2)*H;
이제, 10,000개의 원뿔에 대한 정보를 수집해 보십시오. 벡터 D
와 H
에는 각각 10,000개의 요소가 포함되어 있으며, 지금은 10,000개의 부피값을 계산해야 되는 상황입니다. 대부분의 프로그래밍 언어에서는 다음 MATLAB 코드와 비슷한 루프를 설정해야 합니다.
for n = 1:10000 V(n) = 1/12*pi*(D(n)^2)*H(n); end
MATLAB에서는 스칼라의 경우와 유사한 구문을 사용하여 벡터의 각 요소에 대해 계산을 수행할 수 있습니다.
% Vectorized Calculation
V = 1/12*pi*(D.^2).*H;
참고
연산자 *
, /
, ^
앞에 마침표(.
)를 추가하면 이러한 연산자가 배열 연산자로 변환됩니다.
배열 연산자를 사용하면 차원이 서로 다른 행렬을 결합할 수도 있습니다. 이렇게 크기가 1인 차원을 자동으로 확장하는 기능은 그리드 생성, 행렬과 벡터 연산 등을 벡터화하는 데 유용합니다.
행렬 A
가 테스트 점수를 나타내고, 각 행이 서로 다른 학급을 나타낸다고 가정하겠습니다. 각 학급의 평균 점수와 학생 개인별 점수 간의 차이를 계산하려고 한다고 생각해 봅시다. 루프를 사용할 경우 연산은 다음과 같습니다.
A = [97 89 84; 95 82 92; 64 80 99;76 77 67;... 88 59 74; 78 66 87; 55 93 85]; mA = mean(A); B = zeros(size(A)); for n = 1:size(A,2) B(:,n) = A(:,n) - mA(n); end
이를 보다 직접적으로 계산하는 방법은 루프를 실행할 필요가 없고 속도가 훨씬 빠른 A - mean(A)
를 사용하는 것입니다.
devA = A - mean(A)
devA = 18 11 0 16 4 8 -15 2 15 -3 -1 -17 9 -19 -10 -1 -12 3 -24 15 1
A
가 7×3 행렬이고 mean(A)
1×3 벡터인 경우에도 MATLAB은 행렬과 크기가 같은 것처럼 벡터를 묵시적으로 확장하며, 연산은 정상적인 요소별 뺄셈 연산으로 실행됩니다.
피연산자의 크기 요구 사항은 각 차원의 크기 요구 사항으로, 배열의 크기가 모두 동일하거나 배열 중 하나의 크기가 1이어야 합니다. 이 요구 사항이 충족되면 배열 중 하나의 크기가 1인 차원은 다른 배열의 상응하는 차원과 같은 크기로 확장됩니다. 자세한 내용은 기본 연산에 대해 호환되는 배열 크기 항목을 참조하십시오.
벡터화에 묵시적 확장이 유용하게 쓰이는 또 다른 경우는 다차원 데이터를 사용할 때입니다. 두 변수 x
와 y
의 함수 F
를 실행한다고 가정하겠습니다.
F(x,y) = x*exp(-x2 - y2)
x
벡터와 y
벡터의 점의 모든 조합에 대해 이 함수를 실행하려면 다음과 같이 값의 그리드를 정의해야 합니다. 이 작업에서는 루프를 사용하여 점 조합을 반복해서는 안 됩니다. 대신 벡터 중 하나가 열이고 다른 하나는 행인 경우, 벡터가 x+y
또는 x-y
같은 배열 연산자와 함께 사용되면 MATLAB은 자동으로 그리드를 생성합니다. 이 예제에서 x
는 21×1 벡터이고 y
는 1×16 벡터이기 때문에 이 작업은 x
의 두 번째 차원과 y
의 첫 번째 차원을 확장하여 21×16 행렬을 생성합니다.
x = (-2:0.2:2)'; % 21-by-1 y = -1.5:0.2:1.5; % 1-by-16 F = x.*exp(-x.^2-y.^2); % 21-by-16
논리형 배열 연산
배열의 대량 처리를 논리적으로 확장한다는 것은 비교와 의사결정 작업을 벡터화하는 것입니다. MATLAB 비교 연산자는 벡터 입력값을 받고 벡터 출력값을 반환합니다.
예를 들어, 10,000개의 원뿔에서 데이터를 수집하는 동안 지름에 여러 개의 음수 값을 기록한다고 가정하겠습니다. >=
연산자를 사용하여 벡터에서 유효한 값이 무엇인지 파악할 수 있습니다.
D = [-0.2 1.0 1.5 3.0 -1.0 4.2 3.14]; D >= 0
ans = 0 1 1 1 0 1 1
D
의 요소에 대응하는 유효한 원뿔 부피값 Vgood
을 선별할 수 있습니다.Vgood = V(D >= 0);
MATLAB에서는 함수 all
과 any
를 각각 사용하여 전체 벡터의 요소에 대해 논리적 AND 또는 OR 연산을 수행할 수 있습니다. 다음과 같이, D
의 모든 값이 0보다 작은 경우 경고를 발생시킬 수 있습니다.
if all(D < 0) warning('All values of diameter are negative.') return end
MATLAB은 호환되는 크기의 두 벡터를 비교할 수 있으므로 이를 통해 추가적인 제한을 둘 수 있습니다. 다음 코드는 V가 음수가 아니며 D
가 H
보다 큰 모든 값을 찾습니다.
V((V >= 0) & (D > H))
비교에 도움을 줄 수 있도록, MATLAB에는 오버플로, 언더플로, 정의되지 않은 연산자를 나타내는 특수값(예: Inf
, NaN
)이 있습니다. 논리 연산자 isinf
와 isnan
은 이러한 특수값에 대해 논리 테스트를 수행하는 데 도움이 됩니다. 예를 들어, 다음과 같이 계산에서 NaN
값을 제외시키는 것이 유용할 때가 종종 있습니다.
x = [2 -1 0 3 NaN 2 NaN 11 4 Inf]; xvalid = x(~isnan(x))
xvalid = 2 -1 0 3 2 11 4 Inf
참고
Inf == Inf
는 true를 반환하지만, NaN == NaN
은 항상 false를 반환합니다.
행렬 연산
코드를 벡터화할 때는 특정 크기 또는 구조를 갖는 행렬을 생성해야 하는 경우가 종종 있습니다. 이를 위해 획일한 행렬을 만들 수 있는 기법이 있습니다. 예를 들어, 동일한 요소로 구성된 5×5 행렬이 필요할 수 있습니다.
A = ones(5,5)*10;
v = 1:5; A = repmat(v,3,1)
A = 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
함수 repmat
는 작은 행렬이나 벡터를 더 큰 행렬로 키울 수 있는 유연성을 제공합니다. repmat
는 다음과 같이 입력 행렬을 반복하여 행렬을 만듭니다.
A = repmat(1:3,5,2) B = repmat([1 2; 3 4],2,2)
A = 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 B = 1 2 1 2 3 4 3 4 1 2 1 2 3 4 3 4
정렬, 설정, 셈 작업
여러 애플리케이션에서, 벡터의 요소에 대해 수행되는 계산은 동일한 벡터에 있는 다른 요소에 따라 달라집니다. 예를 들어, 벡터 x는 집합을 나타낼 수 있습니다. for
또는 while
루프를 사용하지 않고 어떻게 집합 요소에 대한 반복 연산을 할 수 있는지는 명백하지 않습니다. 벡터화된 코드를 사용하면 프로세스가 훨씬 더 명확해지고 구문도 덜 복잡해집니다.
중복 요소 제거하기
벡터 내의 중복 요소를 찾는 방법에는 여러 가지가 있습니다. 그중 하나는 함수 diff
를 이용하는 것입니다. 벡터 요소를 정렬한 후에, 이 벡터에 diff
함수를 사용하면 동일한 요소가 인접한 경우 0이 생성됩니다. diff(x)
는 x
보다 요소가 한 개 더 적은 벡터를 생성하기 때문에, 이 집합 내의 요소와 동일하지 않은 요소를 하나 추가해야 합니다. NaN
은 항상 이 조건을 충족합니다. 마지막으로, 다음과 같이 논리형 인덱싱을 사용하여 집합 내의 고유한 요소를 선택할 수 있습니다.
x = [2 1 2 2 3 1 3 2 1 3]; x = sort(x); difference = diff([x,NaN]); y = x(difference~=0)
y = 1 2 3
unique
함수를 사용해도 동일한 작업을 수행할 수 있습니다.y=unique(x);
unique
함수는 필요 이상의 기능을 제공하여 코드 실행 속도를 둔화시킬 수 있습니다. 각 코드 조각(Code Snippet)의 성능을 측정하려면 tic
과 toc
함수를 사용하십시오.벡터의 요소 개수 세기
단순히 x
의 집합이나 부분 집합을 반환하는 대신, 벡터의 요소 개수를 셀 수 있습니다. 벡터가 정렬된 후에 find
함수를 사용하여 diff(x)
에서 값이 0인 인덱스를 파악하고 요소의 값이 변경되는 위치를 표시할 수 있습니다. find
함수의 이후 인덱스 간의 차이는 특정 요소의 개수를 나타냅니다.
x = [2 1 2 2 3 1 3 2 1 3]; x = sort(x); difference = diff([x,max(x)+1]); count = diff(find([1,difference])) y = x(find(difference))
count = 3 4 3 y = 1 2 3
find
함수는 NaN
요소에 대한 인덱스는 반환하지 않습니다. isnan
함수와 isinf
함수를 사용하여 NaN
값과 Inf
값의 개수를 셀 수 있습니다.count_nans = sum(isnan(x(:))); count_infs = sum(isinf(x(:)));
벡터화에 흔히 사용되는 함수
함수 | 설명 |
---|---|
all | 모든 배열 요소가 0이 아닌 값인지 아니면 true인지 확인 |
any | 모든 배열 요소가 0이 아닌 값인지 확인 |
cumsum | 누적합(Cumulative Sum) |
diff | 차분과 근사 도함수 |
find | 0이 아닌 요소의 값이나 인덱스 찾기 |
ind2sub | 선형 인덱스의 첨자 |
ipermute | N차원 배열의 차원 역치환(Inverse Permute) |
logical | 숫자형 값을 논리값(Logical)으로 변환 |
meshgrid | 2차원 공간과 3차원 공간의 사각 그리드(Rectangular Grid) |
ndgrid | N차원 공간의 사각 그리드 |
permute | N차원 배열의 차원 재배열 |
prod | 배열 요소의 곱 |
repmat | 배열의 복사본을 반복함 |
reshape | 배열 형태 변경(reshape) |
shiftdim | 차원 전환 |
sort | 배열 요소 정렬 |
squeeze | 한원소(Singleton) 차원 제거 |
sub2ind | 첨자를 선형 인덱스로 변환 |
sum | 배열 요소의 합 |