레이블이 matrix인 게시물을 표시합니다. 모든 게시물 표시
레이블이 matrix인 게시물을 표시합니다. 모든 게시물 표시

2015년 7월 6일 월요일

넘파이(numpy) 배열(ndarray)와 행렬(matrix)객체 비교

  numpy에는 배열(ndarray)과 행렬(matrix) 객체가 있는데 이들 중 어느 것을 사용해야 하는지 처음 공부할 때 혼동하기 쉽다. 언뜻 보기에는 사용법에 별 차이가 없어 보이고 행렬 연산에는 무조건 matrix를 사용해야 하는 것 아닌가 짐작했었는데 다음 사이트에 잘 정리가 되어 있어서 여기에 옮겨보도록 하겠다.
              numpy for MATLAB users

1. 결론 요약

  일단 결론부터 말하자면 ndarray 를 사용하는 것이 더 효율적이라고 명시되어 있다. 개인적으로 matrix 객체는 MATLAB 사용자의 편의를 위해서 (억지로) 만들어진 것 같은 느낌이 든다. (pylab 과 같은 모듈도 비슷한 이유로 만들어졌으나 파이썬 커뮤니티 안에서는 사용이 역시 권장되지 않는다.)
 ndarray를 사용해야 하는 이유는 다음과 같이 요약할 수 있다.
  • ndarray는 numpy에서 지원하는 표준형인 벡터/행렬/텐서 를 저장한다.
  • 많은 numpy 함수가 matrix가 아니라 ndarray를 반환한다.
  • 요소 간 연산과 선형대수 연산에 대해선 명확히 구분되어 있다.
  • 표준 벡터나 열벡터/행벡터를 표현할 수 있다.
ndarray를 사용할 경우의 한 가지 단점은 행렬의 곱셈을 수행할 때 ,dot() method를 사용해야한다는 점이다. 즉, ndarray 객체 A와 B를 행렬곱하려면 A.dot(B) 와 같이 수행해야 하며 A*B는 요소간 곱셈이 된다. 반면 matrix객체 A와 B는 단순히 A*B로 행렬곱이 수행된다.

2. 좀 더 자세한 설명

 numpy는 ndarray 와 matrix 둘 다 포함한다. ndarray는 다양한 종류의 수치 연산을 위해서 고안된 범용 n차 배열이다. 반면 matrix는 선형 대수 연산을 위해서 특별히 고안된 객체이다. 실용적인 관점에서 둘 사이의 차이점은 몇 가지 안된다.
 
  • 연산자 *, dot() 그리고 multiply()
    • ndarray 는 '*'는 요소간 곱셈이다. 행렬곱을 할때는 obj.dot() 메쏘드를 사용해야 한다.
    • 반면 matrix는 '*'이 행렬곱이다. 그리고 numpy.multiply() 함수가 요소간 곱이다.
  • 벡터와 1차 배열
    • ndarray 는 벡터 1xN, Nx1, 그리고 N크기의 1차원 배열이 모두 각각 다르다. obj[:,1] 는 크기가 n인 1차 배열을 반환한다. 그리고 1차원 배열의 전치는 작동하지 않는다. (역자 주 : ndarray로 벡터를 표현할 때는 반드시 2차 배열을 이용해야 한다.)
    • 반면 matrix 객체에서 1차 배열은 모두 1xn, 혹은 nx1 행렬(2차원 배열)로 상향 변환된다. ( matrix 객체는 내부적으로 항상 2차원 배열이다. )
  • ndarray는 고차원 배열이 가능하지만 matrix는 항상 2차원 배열 이다.
  • 편리한 attribute
    • ndarray 는 전치를 해주는 a.T attribute 가 있다.
    • matrix 도 m.H, m.I, m.A attribute들이 있으며 각각 복소전치, 역행렬, ndarray로의 변환이다.
  • 편리한 생성자
    • ndarray는 중첩된 리스트로 다차원 배열을 생성한다. 예) array( [ [1,2,3].[4,5,6] ] )
    • matrix는 MATLAB 의 문법을 지원한다. 예) matrix('[1 2 3; 4 5 6]')

좀 더  부가 설명을 하자면 ndarray를 이용하여 벡터(vector)를 표현할 때는 2차 배열로 정의해야 한다. 즉, 다음 세 가지는 모두 다르며 이 중  벡터는 두 번째와 세 번째 같이 생성해야 한다. (혼동하기 참 쉽다.)

a1 = np.array( [1, 2, 3] ) #크기 (3,)인 1차원 배열
a2 = np.array( [ [1, 2, 3] ] ) #크기 (1,3)인 2차원 배열 (행벡터)
a3 = np.array( [ [1], [2], [3] ] ) #크기 (3,1)인 2차원 배열 (열벡터)

여기서 a1.T 는 동작하지 않는다. 반면 a2.T 와 a3.T는 동작한다. 1차 배열은 행벡터나 열벡터 두 가지 모두로 취급되기도 한다.

 ndarray 객체를 사용하는데 있어서 장점과 단점은 다음과 같다.

  • 장점
    • 1차 배열은 행벡터나 열벡터 둘 다로 취급할 수 있다. dot(A,v) 에서 v는 열벡터로 다루어지고 dot(v,A)에서는 행벡터로 취급된다. 따라서 전치를 복잡하게 수행할 필요가 없다.
    • 요소 간 곱셈이 쉽다 (예: A*B) 사실 모든 산술 연산 (+ - * / ** 등등)이 요소 간 연산이다.
    • ndarray 는 numpy 의 가장 기본 객체이므로 연산의 속도, 효율성 그리고 numpy를 이용하는 외부 라이브러리의 반환형이 이것일 가능성이 높다.
    • 다차원 배열을 쉽게 구현한다.
    • tensor algebra 에 대한 장점이 있다.(?)
  • 단점
    • 행렬간 곱에 obj.dot() 멤버함수를 사용해야 하므로 번잡할 수 있다. 세 행렬 A, B, C의 행렬곱은 dot( dot(A,B),C) 이다

matrix 객체를 사용하는 데 있어서 장점과 단점은 다음과 같다.

  • 장점
    • MATLAB 행렬과 비슷하게 동작한다.
    • 행렬곱이 A*B 와 같이 '*' 연산자이므로 선형대수 연산이 좀 더 편하다.
  • 단점
    • matrix 의 차수는 항상 2이다. 벡터도 2차 배열로 저장된다. 3차 행렬을 구현하려면 ndarray를 이용하던가 matrix객체를 요소로 갖는 리스트(list)로 구현해야 한다.
    • ndarray 가 numpy의 표준 객체이기 때문에 어떤 함수는 인수로 matrix를 주어도 반환 객체는 ndarray 이기도 하다. numpy 에 포함된 함수는 절대 그렇지 않지만 제3자 라이브러리는 그럴 수 있다.
    • 요소간 곱은 multiply(A,B)함수를 이용해야 한다.
    • *은 행렬곱인데 반해 /는 요소간 연산이다. 즉, 연산자 오버로딩에 일관성이 결여되어 있다.

 개인적으로는 numpy를 사용하기로 결정되었다면 matlab에서의 개념은 모두 버리고 numpy에서 제공하는 객체에 빨리 익숙해져야한다는 의견이다. 그래서 matrix객체나 pylab 모듈을 사용하는 것이 권장되지 않는 것이다.


2015년 6월 17일 수요일

싸이랩(scialb)의 특수 행렬을 생성하는 함수들

 프로그램을 작성하다 보면 빈행렬(empty matrix)을 생성할 필요가 가끔씩 생긴다. 즉, 요소가  없는 0x0크기의 빈행렬를 생성하려면 다음과 같이 하면 된다.

>> A = []
 
만약 행렬 A가 이전에 어떤 요소들을 가지고 있었다면 빈행렬을 대입하는 순간 그것들은 지워지고  그 요소를 저장하기 위해 할당되었던 메모리는 반환된다.
특수한 행렬을 생성하는 기본적인 함수들은 다음 표와 같다.

[표 1] 특수 행렬을 생성하는 함수들
eye(), eye(r, c), eye(M)
주대각 요소가 1인 행렬을 생성한다.
zeros(), zeros(r, c), zeros(M)
모든 요소가 0인 행렬 생성
ones(), ones(r, c), ones(M)
모든 요소가 1인 행렬 생성
linspace(c1, c2 [, n])
c1부터 c2까지 n개의 등간격 행벡터 생성. (n이 생략되면 기본적으로 100임) c1, c2 : 복소수 가능
testmatrix(‘magi’, n)
testmatrix(‘frk’, n)
testmatrix(‘hilb’, n)
nxn 매직행렬 생성
nxn Franck행렬 생성
nxn Hilbert행렬의 역행렬 생성
rand(“seed”), rand(“seed”, s)
rand(r, c [ , strKey] )
rand(M [ , strKey] )
seed값을 얻거나 설정한다. (초기에 s=0임)
r x c 크기 혹은 M행렬과 같은 크기의 난수 행렬 생성
strKey : “uniform”, “normal”, “info”
grand
더 다양한 옵션으로 난수행렬 생성
eye(M)은 M행렬과 같은 차수이고 주대각 요소가 1인 행렬을 생성하며 행렬M이 꼭 정방행렬일 필요는 없다. 주의할 점은 eye(10)이라고 입력하면 10x10 행렬이 생성되는 것이 아니라 1x1행렬이 생성된다는 것이다. (10이라는 숫자가 1x1행렬이기 때문) 그리고 덧셈과 뺄셈에서 사용될 때는 더해지는 행렬과 자동으로 같은 차수의 행렬을 생성한다. ones()와 zeros()도 같은 특성을 가진다.
 linspace(c1, c2 [,n])함수는 c1부터 c2까지 등간격으로 n개의 요소를 가지를 행벡터를 생성하는 자주 사용되는 함수이다. 예를 들어서 0부터 2π 까지 100개의 요소를 가지는 행벡터를 생성헤서 vX변수에 대입하려면 다음과 같이 하면 된다.
>> vX =  linspace(0, 2*%pi)
1000개로 개수를 늘리려면 다음과 같이 한다.

>> vX =  linspace(0, 2*%pi, 1000)
 
 한 가지 특이한 것은 c1, c2는 복소수도 가능하다는 것이다. 예를 들어서 c1=1+%i, c2=2+3*%i, n=5라면 간격을 나눌 때 실부부는 실수부끼리, 허수부는 허수부끼리 나눈다. 즉 다음 두 명령은 같은 것이다.
>> linspace(1+%i, 2+3*%i, 5)
>> linspace(1, 2, 5) + linspace(1, 3, 5)*%i

후자 보다는 전자가 더 간단하므로 이것을 사용하면 된다
.
 함수 rand()는 난수행렬을 생성시킨다. 이때 균일분포/정규분포를 strKey로 지정할 수 있다.

위 예제에서 histplot()은 히스토그램을 그리는 함수이다.


싸이랩(scialb)의 행렬 연산

(A) 덧셈과 뺄셈

 두 행렬의 덧셈/뺄셈은 크기(행수와 열수)가 같아야 수행되며 다르면 에러가 발생한다. 한 가지 주의할 것은 <스칼라±행렬> 의 연산인데 수학적으로는 정의되지 않지만 Scilab 에서는  (Matlab 와 마찬가지로) 스칼라를 모든 요소와 각각 더하는 연산을 한다는 것이다.
>> A=[1 2;3 4]; B=[5*%i %pi;%e %e^2]; C=[1 2 3]; d=-2;
>> A+B
ans  =
   1. + 5.i     5.1415927  
   5.7182818    11.389056  
>> B-C
    !--error 9
 Inconsistent subtraction.
>> C+d
ans  =
 - 1.    0.    1.
위 의 예에서 보듯이 행렬 A와 B는 크기가 둘 다 2x2로 같으므로 덧셈/뺄셈이 가능하지만 B와 C는 크기가 다르다. 따라서 B와 C의 덧셈/뺄셈은 허용되지 않는다. 다만 d는 스칼라이므로 이것과 행렬과의 연산은 가능하며 모든 요소에 이 스칼라를 더하거나 빼는 연산을 수행한다.

(B) 곱셈

 행렬의 곱셈에 사용되는  연산자들은 다음 표와 같다.
[표 1]  행렬의 곱셈
A*B
일반적인 곱셈. A의 행과 B의 열의 개수는 같아야 함.
A.*B
요소간 곱셈. A와 B는 같은 크기여야 함.
A.*.B
Kronecker 곱셈.
A^n, A**n
A행렬의 n승. 즉, A*A*A*...*A. A는 정방행렬이어야 함.
A.^B
요소간 거듭제곱. A와 B는 같은 크기여야 함.
단순한 A*B 연산은 행렬의 곱셈으로서 피연산자의 한 쪽이 스칼라 이거나 아니면 A의 행 수와 B의 열의 개수는 같아야 계산이 성립한다.
>> A=[1,2+3*%i;%pi %e^2]
A  =
   1.           2. + 3.i  
   3.1415927    7.3890561

>> %i*A
ans  =
    i           - 3. + 2.i    
  3.1415927i    7.3890561i  

>> A*[1 2 3;4 5 6]
ans  =
   9. + 12.i    12. + 15.i    15. + 18.i  
   32.697817    43.228466     53.759115

>> [1 2 3]*A
         !--error 10
Inconsistent multiplication.
위의 예들에서 마지막 것은 행렬의 차수가 맞지 않아서 에러가 발생하는 것이다.

 이에 반해서 A.*B 연산은 행렬의 같은 위치에 있는 요소끼리 곱하는 연산으로서 A행렬과 B행렬의 크기가 같아야 한다.

>> P=[1 2 ; 3 4]
P  =
    1.    2.  
    3.    4.  

>> Q= [%i %pi; %e %T]
Q  =
    i            3.1415927  
    2.7182818    1.        

>> P.*Q
ans  =
    i            6.2831853  
    8.1548455    4.        
이 연산의 결과도 같은 크기의 행렬이다. 만약 크기가 서로 다른 행렬에 대해서 이 연산을 수행하려고 한다면 에러를 발생할 것이다.

>> [1 2].*[3;4]
         !--error 9999
inconsistent element-wise operation
 
그리고 A.*.B 연산은 Kronecker 곱셈으로서 수학기호로는 ⊗로 표시 한다. 즉 A⊗B는 다음과 같은 연산을 수행한다.


따라서 행렬 A와 B에 대한 크기 제약 조건은 없으며 예를 들면 다음과 같다.

>> [1 2].*.[10 20;30 40]
ans  =
   10.    20.    20.    40.  
   30.    40.    60.    80.
 
 행렬의 거듭제곱에는 ^, **, .^ 연산자가 있다. A^n, 또는 A**n 은 행렬의 거듭제곱 연산자로서 예를 들어서 A^3 또는 A**3은 A*A*A 와 결과가 같다. 따라서 이 연산이 수행되려면 A행렬은 반드시 정방행렬이어야 한다. 이에 반해서 A.^n 은 행렬의 각각의 요소를 n승 하는 것이다. 따라서 A행렬의 크기에 대한 제한 조건은 없다.
>> A=[2,3;4,5];
>> A^3
ans  =
    116.    153.  
    204.    269.  
>>A.^3
ans  =
    8.     27.  
   64.    125.  
행렬의 거듭 제곱과 요소간 거듭 제곱은 반드시 구분해야 한다. 한 가지 특이한 (혼동하기 쉬운)점은 만약 A가 벡터라면 A^n 이나 A.^n 이나 결과가 같다는 것이다. (개인적인 생각으로는 A가 벡터일 때 A^n 연산은 에러를 뱉는 것이 더 일관성이 있는 것 아닌가 하는 아쉬움은 있는데 어쨌든 Scilab은 그렇게 동작한다.(

(C) 나눗셈

 행렬의 나눗셈에 대해서 다음 표에 정리하였다. 나눗셈은 보통의 나눗셈과 좌나눗셈으로 나뉜다는 점이 특이하다.
[표 2]  행렬의 나눗셈
A/B
행렬의 나눗셈. A*inv(B)와 같다.
한 쪽이 스칼라이거나 크기가 같아야 한다.
A./B
요소간 나눗셈. A와 B는 같은 크기여야 한다..
A\B
행렬의 좌나눗셈 inv(A)*B와 같다.
한 쪽이 스칼라이거나 크기가 같아야 한다.
A.\B
요소간 좌나눗셈. A와 B는 같은 크기여야 한다.
이 경우에도 요소간 연산자가 따로 정의되어 있으며 dot(.)으로 시작한다. 또한 행렬의 나눗셈의 경우는 역행렬과 관계되어 있으며 A/B는 A*inv(B)와, A\B는 inv(A)*B와 동일한 연산을 수행한다. inv()함수는 정방행렬의 역행렬을 구하는 함수이다.

(D) 기타 연산자

  행렬의 복소전치행렬은 작은 따옴표(‘)를 행렬의 끝에 붙여주면 구할 수 있고 단순 전치 행렬은 점 작은 따옴표 (.’)를 붙이면 된다. 복소전치행렬은 행과 열의 위치를 바꾸고 켤레복소수로 변환된 행렬을 의미하며 단순 전치 행렬은 그냥 위치만 바꾼 행렬은 의미한다.
>> A=[1, 2*%i; 3+4*%i, 5]
 A  =
    1.          2.i  
    3. + 4.i    5.  

>> B=A'
B  =
    1.     3. - 4.i  
 - 2.i    5.        

>> C=A.'
C  =
   1.     3. + 4.i  
   2.i    5.      

 

이 결과를 잘 살펴보면 B행렬은 A행렬의 복소 전치 행렬이고 C행렬은 단순 전치 행렬이라는 것을 알 수 있다.

 정방행렬의 역행렬을 구하는 함수는 inv()이고 행렬식(determinant)을 구하는 함수는 det()이다. 직전의 A행렬에 대해서 역행렬과 행렬식을 구하는 예는 다음과 같다.

>> inv(A)
 ans  =
    0.3170732 + 0.1463415i    0.0585366 - 0.1268293i  
   - 0.0731707 - 0.3414634i    0.0634146 + 0.0292683i  

>> det(A)
   ans  =
      13. - 6.i