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 모듈을 사용하는 것이 권장되지 않는 것이다.




Posted by 살레시오
,

 프로그램을 작성하다 보면 빈행렬(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()은 히스토그램을 그리는 함수이다.



Posted by 살레시오
,

(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  



Posted by 살레시오
,

 Scilab의 기본 데이타형은 행렬(matrix)이며 스칼라도 내부적으로는 1x1행렬로 취급된다. 벡터도 마찬가지로 nx1 행렬 (열벡터) 혹은 1xn 행렬 (행벡터) 로 취급된다. 행렬의 요소는 숫자, 불리언, 문자열, 다항식 등 일반적인 Scilab 객체가 될 수 있다. 일 반적으로 그냥 벡터라 하면 열벡터(column vector)를 의미하는 경우가 많으므로 앞으로 그냥 벡터라고 하면 열벡터를 지칭하며 행벡터는 명시적으로 표기하도록 하겠다.


 함수에 넘겨주는 입력 파라메터도 행렬이고 함수의 수행 결과 생성되는 출력값도 역시 행렬이다. 편의상  다음과 같이 구분한다.

❶ 스칼라 : 1x1 차원의 행렬

❷ 벡터 : nx1 차원의 행렬 (행벡터는 1xn 행렬), n ≥ 1 인 정수(스칼라 포함)

❸ 행렬 : mxn 차원의 행렬, m,n ≥ 1 인 정수 (스칼라, 벡터포험)

 행렬을 입력할 때는 대괄호 [ ] 를 사용하며 콤마(,)나 공백을 이용하여 요소 간을 구별한다. 세미콜론(;) 혹은 다음 줄이 행들을 구분한다. (이것은 Matlab 이나 Octave와 동일하다.) 따라서 다음 네 가지가 완전히 동일한 2x3 행렬을 생성한다.

>> A = [1, 2, 3; 4, 5, 6]
>> A = [1 2 3; 4 5 6]
>> A = [1,2,3
4,5,6]
>> A = [1 2 3
4 5 6]

행이 두 개 이상일 때는 반드시 행들의 요소 갯수가 같아야 함에 유의해야 한다. 예를 들어서 다음과 같은 입력은 에러를 발생시킬 것이다. 왜냐하면 1행의 크기는 3인데 2행의 크기는 2이기 때문이다.


>> B = [ 1 2 3; 4 5]

행렬의 요소는 복소수도 가능하고 문자열도 가능하다. 예를 들어 다음과 같다.

복소행렬을 생성할 때 주의할 점은 실수부와 허수부사이에 공백문자가 있으면 안된다는 것이다. 다음의 C행렬과 D행렬은 크기가 다른 행렬이 되버린다.(왜?)


>> C = [2+3*%i, 4]
>> D = [2 +3*%i, 4]

 

문자열을 요소로 하는 행렬도 가능하다. 예를 들면 다음과 같다.

>> E = [“abc” “def”]
>> F = [“hello”, “world”; “My name is”, ‘Salecio’]    

그러나 한 행렬의 요소들이 숫자나 문자열이 섞일 수는 없다. 또한 정수형과 실수형은 데이터형이 다르기 때문에 한 행렬에 섞일 수도 없다. 따라서 다음과 같은 입력은 에러를 발생시킨다.


>> G = [ 1 2; ‘abc’, 4]
>> K = [int8(1) 2; 3 4]

 

첫 번째 G는 숫자와 문자열을 한 행렬에 섞어서 입력했기 때문에 에러를 발생하며 두 번째 K는 정수형과 실수형이 섞여있기 때문에 에러를 발생한다.

 반면에 진리값을 나타내는 %T(혹은 %t) 와 %F( 혹은 %f)는 특수한 상수이지만 내부적으로 (더블형) 실수 1과 0으로 취급된다. 따라서 이들 값을 숫자형과 섞어서 사용할 수 있다.

이미 생성된 행렬을 이용하여 새로운 행렬을 생성할 수도 있다.

>> A = [ 1 2; 3 4]
>> B = [ A; 5 6]
>> C = [ A [5 6; 7 8] ]
>> D = [A A]
>> E = [A; A]

두 행렬을 옆으로 연결할 때에는 행의 수가 같아야하고 위아래로 연결할 때는 열의 수가 같아야 에러가 발생하지 않는다.

콜론(:) 연산자

 행벡터 혹은 열벡터를 입력하거나, 생성시켜야 하는 경우에 각 벡터 요소들의 증가분이나  감소분이 일정한 경우가 있다. 이 경우에는 콜론(:) 연산자를 이용하면 된다. 이 연산자는 사용 빈도가 높으므로 잘 숙지해 두어야 한다. 입력형식은 아래와 같다.


>> 변수 = 초기값:증감값:최종


이 명령은 초기값으로부터 최종값까지 증감값만큼 증감시킨 수열을 요소로 갖는 행벡터를 생성하여 변수에 저장하게 된다. (행벡터를 생성한다는데 주의하자!) 예를 들어, 변수 a에 0부터 100까지 10의 간격으로 행벡터를 만들고자 한다면 다음과 같이 입력하여 실행한다.



만약 증가분이 1이라면 이것은 생략할 수 있다. 만약 0부터 20까지 1씩 증가시킨 값들을 요소로 갖는 행벡터를 만들고 싶다면 다음과 같이 한다.


>> b=1:20

 

10부터 1씩 감소시켜서 0까지의 값을 요소로 갖는 행벡터는 다음과 같이 생성할 수 있다.

>> c = 10:-1:0

콜론 연산자는 사용 빈도가 굉장히 높으므로 잘 숙지하고 있어야 한다.



Posted by 살레시오
,