이번 포스팅에서는 1차원 배열 내 고유한 원소 집합 (a set with unique elements) 을 찾고, 더 나아가서 고유한 원소별 개수(counts per unique elements)도 세어보고, 원소 개수를 기준으로 정렬(sorting)도 해보는 여러가지 방법을 소개하겠습니다.

 

 

(1) numpy 1D 배열 안에서 고유한 원소 집합 찾기
    (finding a set with unique elements in 1D numpy array)

(2) numpy 1D 배열 안에서 고유한 원소 별로 개수 구하기
    (counts per unique elements in 1D numpy array)

(3) numpy 1D 배열 안에서 고유한 원소(key) 별 개수(value)를 사전형으로 만들기

    (making a dictionary with unique sets and counts of 1D numpy array)

(4) numpy 1D 배열의 고유한 원소(key) 별 개수(value)의 사전을 정렬하기

    (sorting a dictionary with unique sets and counts of 1D numpy array)

(5) numpy 1D 배열을 pandas Series 로 변환해서 고유한 원소 별 개수 구하고 정렬하기

    (converting 1D array to pandas Series, and value_counts(), sort_values())

(6) numpy 1D 배열을 pandas DataFrame으로 변환해 고유 원소별 개수 구하고 정렬하기

    (converting 1D array to pandas DataFrame, and value_counts(), sort_values())

 

 

 

 

먼저, 예제로 사용할 간단한 numpy 1D 배열을 만들어보겠습니다.

 

## simple 1D numpy array

import numpy as np

arr = np.array(['a', 'c', 'c', 'b', 'a', 
                'b', 'b', 'c', 'a', 'c', 
                'b', 'a', 'a', 'a', 'c'])
                
                
arr
[Out] array(['a', 'c', 'c', 'b', 'a', 'b', 'b', 'c', 'a', 'c', 
             'b', 'a', 'a', 'a', 'c'], dtype='<U1')
             

 

 

(1) numpy 1D 배열 안에서 고유한 원소 집합 찾기
    (finding a set with unique elements in 1D numpy array)

 

np.unique() 메소드를 사용하면 numpy 배열 내 고유한 원소(unique elements)의 집합을 찾을 수 있습니다.

 

## np.unique(): Find the unique elements of an array
np.unique(arr)
[Out] 
array(['a', 'b', 'c'], dtype='<U1')

 

 

더 나아가서, return_inverse=True 매개변수를 설정해주면, 아래의 예처럼 numpy 배열 내 고유한 원소의 집합 배열과 함께 '고유한 원소 집합 배열의 indices 의 배열' 을 추가로 반환해줍니다.

따라서 이 기능을 이용하면 array(['a', 'c', 'c', 'b', 'a', 'b', 'b', 'c', 'a', 'c', 'b', 'a', 'a', 'a', 'c']) 를 ==> array([0, 2, 2, 1, 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 2]) 로 쉽게 변환할 수 있습니다.

 

## return_inverse=True: If True, also return the indices of the unique array
np.unique(arr, 
          return_inverse=True)
[Out]
(array(['a', 'b', 'c'], dtype='<U1'),
 array([0, 2, 2, 1, 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 2]))
 

 

 

 

(2) numpy 1D 배열 안에서 고유한 원소 별로 개수 구하기
    (counts per unique elements in 1D numpy array)

 

위의 (1)번에서 np.unique() 로 numpy 배열 내 고유한 원소의 집합을 찾았다면, return_counts = True 매개변수를 설정해주면 각 고유한 원소별로 개수를 구해서 배열로 반환할 수 있습니다.

 

## return_counts: If True, also return the number of times each unique item appears in ar.
np.unique(arr, 
          return_counts = True)     

[Out]
(array(['a', 'b', 'c'], dtype='<U1'), array([6, 4, 5]))

 

 

 

(3) numpy 1D 배열 안에서 고유한 원소(key) 별 개수(value)를 사전형으로 만들기

    (making a dictionary with unique sets and counts of 1D numpy array)

 

위의 (2)번에서 각 고유한 원소별 개수를 구해봤는데요, 이를 파이썬의 키:값 쌍 (key: value pair) 형태의 사전(dictionary) 객체로 만들어보겠습니다.

 

먼저 np.unique(arr, return_counts = True) 의 결과를 unique, counts 라는 이름의 array로 할당을 받고, 이를 zip(unique, counts) 으로 쌍(pair)을 만들어준 다음에, dict() 를 사용해서 사전형으로 변환해주었습니다.

 

## making a dictionary with unique elements and counts of 1D array
unique, counts = np.unique(arr, return_counts = True)
uniq_cnt_dict = dict(zip(unique, counts))

uniq_cnt_dict
[Out]
{'a': 6, 'b': 4, 'c': 5}

 

 

 

(4) numpy 1D 배열의 고유한 원소(key) 별 개수(value)의 사전을 정렬하기

    (sorting a dictionary with unique sets and counts of 1D numpy array)

 

위의 (3)번까지 잘 진행을 하셨다면 이제 (unique : counts) 쌍의 사전을 'counts' 의 값을 기준으로 오름차순 정렬(sorting a dict by value in ascending order) 또는 내림차순 정렬 (sorting a dict by value in descending order) 하고 싶은 마음이 생길 수 있는데요, 이럴 경우 sorted() 메소드를 사용하면 되겠습니다. (pytho dictionary 정렬 참조: rfriend.tistory.com/473)

 

## sorting a dictionary by value in ascending order
## -- reference: https://rfriend.tistory.com/473
sorted(uniq_cnt_dict.items(), 
       key = lambda x: x[1])
       
[Out]
[('b', 4), ('c', 5), ('a', 6)]


## sorting a dictionary by value in descending order
sorted(uniq_cnt_dict.items(), 
       reverse = True, 
       key = lambda x: x[1])
       
[Out]
[('a', 6), ('c', 5), ('b', 4)]

 

 

 

(5) numpy 1D 배열을 pandas Series 로 변환해 고유한 원소별 개수 구하고 정렬하기

    (converting 1D array to pandas Series, and value_counts(), sort_values())

 

pandas 의 Series 나 DataFrame으로 변환해서 데이터 분석 하는 것이 더 익숙하거나 편리한 상황에서는 pandas.Series(array) 나 pandas.DataFrame(array) 로 변환을 해서, value_count() 메소드로 원소의 개수를 세거나, sort_values() 메소드로 값을 기준으로 정렬을 할 수 있습니다.

 

import pandas as pd

## converting an array to pandas Series
arr_s = pd.Series(arr)
arr_s
[Out]
0     a
1     c
2     c
3     b
4     a
5     b
6     b
7     c
8     a
9     c
10    b
11    a
12    a
13    a
14    c
dtype: object


## counting values by unique elements of pandas Series
arr_s.value_counts()
[Out]
a    6
c    5
b    4
dtype: int64


## sorting by values in ascending order of pandas Series
arr_s.value_counts().sort_values(ascending=True)
[Out]
b    4
c    5
a    6
dtype: int64

 

 

(6) numpy 1D 배열을 pandas DataFrame으로 변환해 고유한 원소별 개수 구하고 정렬하기

    (converting 1D array to pandas DataFrame, and value_counts(), sort_values())

 

만약 pandas Series 내 고유한 원소별 개수를 구한 결과를 개수의 오름차순으로 정렬을 하고 싶다면 sort_values(ascending = True) 를 설정해주면 됩니다. (내림차순이 기본 설정, default to descending order)

 

import pandas as pd

## converting an array to pandas DataFrame
arr_df = pd.DataFrame(arr, columns=['x1'])
arr_df

[Out]
x1
0	a
1	c
2	c
3	b
4	a
5	b
6	b
7	c
8	a
9	c
10	b
11	a
12	a
13	a
14	c


## counting the number of unique elements in Series
arr_df['x1'].value_counts()
[Out]
a    6
c    5
b    4
Name: x1, dtype: int64


## # sorting by the counts of unique elements in ascending order
arr_df['x1'].value_counts().sort_values(ascending=True)
[Out]
b    4
c    5
a    6
Name: x1, dtype: int64

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)

 

Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python numpy 의 배열의 원소 값을 사전(dictionary)의 {키: 값} 쌍 ({key: value} pair) 을 이용해서, 배열의 원소 값과 사전의 키를 매핑하여 사전의 값으로 배열의 원소값을 변환하는 방법을 소개하겠습니다.

 

아래의 예에서는 다중분류 (multi-class classification) 기계학습 모델로 부터 각 관측치가 5개 classes 별 속할 확률을 배열로 반환받은 상황을 가정하여 만들어보았습니다.

 

(1) 다중분류 확률 배열로 부터 최대값의 위치 인덱스 가져오기

(2) np.vectorize() 와 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

(3) for loop 과 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

 

 

 

(1) 다중분류 확률 배열로 부터 최대값의 위치 인덱스 가져오기

 

먼저, 5개 class를 가지는 다중분류 문제에서 5개 class 별 속할 확률을 기계학습 분류 모델로 부터 아래의 'pred_proba' 라는 이름의 배열로 얻었다고 가정해보겠습니다.

 

import numpy as np

## probability for each classes
pred_proba = np.array([[0., 0., 0.2, 0.8, 0.], 
                       [0.9, 0., 0., 0., 0.1], 
                       [0., 0., 0.6, 0.2, 0.2], 
                       [0., 0., 0.5, 0.3, 0.2], 
                       [0., 0.1, 0.3, 0., 0.6], 
                       [0., 0.4, 0., 0.3, 0.3]])

pred_proba
[Out]
array([[0. , 0. , 0.2, 0.8, 0. ],
       [0.9, 0. , 0. , 0. , 0.1],
       [0. , 0. , 0.6, 0.2, 0.2],
       [0. , 0. , 0.5, 0.3, 0.2],
       [0. , 0.1, 0.3, 0. , 0.6],
       [0. , 0.4, 0. , 0.3, 0.3]])

 

 

이들 확률값 배열로 부터 하나의 예측값을 구하기 위해 이들 5개 각 class별 확률 중에서 가장 큰 값을 가지는 위치 (indices of maximum value) 의 class 를 모델이 예측한 class 라고 정의해보겠습니다.  

np.argmax(pred_proba, axis=1) 은 배열 내의 각 관측치 별 (axis = 1) 로 가장 큰 확률값의 위치의 인덱스를 반환합니다.  가령, 위의 pred_proba 의 첫번째 관측치의 5개 class 별 속할 확률은 [0., 0., 0.2, 0.8, 0.] 의 배열로서, 확률 0.8 이 가장 큰 값이므로 위치 인덱스 '3'을 반환하였습니다.

 

## positional index for maximum probability
pred_idx = np.argmax(pred_proba, axis=1)
pred_idx
[Out]
array([3, 0, 2, 2, 4, 1])

 

 

(2) np.vectorize() 와 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

 

위의 (1)번에서 구한 확률 최대값의 위치 인덱스 가지고, 이번에는 아래의 'class_map_dict'와 같이 {키: 값} 쌍 사전의 '키(key)'를 기준으로 매핑을 해서, 다중분류 모델의 예측값을 'class 이름'으로 변환을 해보겠습니다.

 

## dictionary with pairs of {index_max_proba: class_name}
class_map_dict = {
    0: 'noraml', 
    1: 'class01', 
    2: 'class02', 
    3: 'class03',
    4: 'class04'
}

class_map_dict
[Out]
{0: 'noraml', 1: 'class01', 2: 'class02', 3: 'class03', 4: 'class04'}

 

 

 

이때 dict.get(key) 를 유용하게 사용할 수 있습니다. dict.get(key) 메소드는 사전(dict)의 키에 쌍으로 대응하는 값을 반환해줍니다. 따라서 바로 위에서 정의해준 'class_map_dict'의 키 값을 넣어주면, 각 키에 해당하는 'normal'~'class04' 의 사전 값을 반환해줍니다.

 

## get() returns the value for the specified key if key is in dict.
class_map_dict.get(pred_idx[0])
[Out]
'class03'


class_map_dict.get(0)
[Out]
'noraml'

 

 

사전의 (키: 값)을 매핑하려는 배열 내 원소가 많을 경우, np.vectorize() 메소드를 이용하면 매우 편리하고 또 빠르게 사전의 (키: 값)을 매핑을 해서 배열의 값을 변환할 수 있습니다. 아래 예에서는 'class_map_dict' 의 (키: 값) 사전을 사용해서 'pred_idx'의 확률 최대값 위치 인덱스 배열을 'pred_cls' 의 예측한 클래스(레이블) 이름('normal'~'class04')으로 변환해주었습니다.

 

np.vectorize() 는 numpy의 broadcasting 규칙을 사용해서 매핑을 하므로 코드가 깔끔하고, for loop을 사용하지 않으므로 원소가 많은 배열을 처리해야 할 경우 빠릅니다.

 

## vectorization of dict.get(array_idx) for all elements of array
pred_cls = np.vectorize(class_map_dict.get)(pred_idx)

pred_cls
[Out]
array(['class03', 'noraml', 'class02', 'class02', 'class04', 'class01'],
      dtype='<U7')
      

* np.vectorize() reference: numpy.org/doc/stable/reference/generated/numpy.vectorize.html

 

 

 

(3) for loop 과 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

 

만약 위의 (2)번 처럼 np.vectorize() 메소드를 사용하지 않는다면, 아래처럼 for loop 사용해서 확률 최대값 위치 인덱스의 개수 만큼 순환 반복을 하면서 dict.get() 함수를 적용해주어야 합니다. 위의 (2)번 대비 코드도 길고, 또 대상 배열이 클 경우 시간도 더 오래 걸리므로 np.vectorize() 사용을 권합니다.

 

## manually using for loop
pred_cls_mat = np.empty(pred_idx.shape, dtype='object')

for i in range(len(pred_idx)):
    pred_cls_mat[i] = class_map_dict.get(pred_idx[i])
    
pred_cls_mat
[Out]
array(['class03', 'noraml', 'class02', 'class02', 'class04', 'class01'],
      dtype=object)

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)

 

Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python Numpy 의 배열(array)을 특정 형상(shape)으로 변형할 때 빈 자리를 '0'이나 다른 값으로 채우는 2가지 방법을 소개하겠습니다.

1. numpy.pad() 함수를 사용하여 배열(array)을 특정 형상의 배열로 변형할 때 빈자리를 '0'으로 채우기
2. tensorflow.keras.preprocessing.sequence.pad_sequence() 함수를 사용하여 배열의 원소 개수가 다른 Ragged array 를 특정 형상의 배열로 바꾸면서 빈자리를 '0'으로 채우기


1. numpy 배열을 특정 형상의 배열로 변형할 때 빈자리를 '0'으로 채우기 (padding)

    : numpy.pad() 함수



먼저, numpy 라이브러리를 importing 하고, 예제로 사용할 2 by 3 의 간단한 2차원 배열(array)을 만들어보겠습니다.




import numpy as np


x = np.array([[1, 2, 3],
                 [7, 2, 5]])
print(x)

array([[1, 2, 3],
          [7, 2, 5]])




위의 2 by 3 의 2차원 배열 x 의 위, 아래에 1개씩의 행을 추가하고, 왼쪽, 오른쪽에 1개씩의 열을 추가하여 4 by 5 의 2차원 배열을 만들되, 새로 추가되는 행과 열의 자리는 '0'으로 채워넣기(padding)를 numpy.pad() 함수를 사용하여 해보겠습니다.

numpy.pad(array, pad_width, mode='constant', **kwargs)


# np.pad(x, (1, 1))

np.pad(x, (1, 1),
       mode='constant',
       constant_values=0)


array([[0, 0, 0, 0, 0]

    [0, 1, 2, 3, 0],     [0, 7, 2, 5, 0],     [0, 0, 0, 0, 0]])




만약 위의 행 1개 추가, 왼쪽 열 1개 추가, 아래쪽 행 2개 추가, 오른쪽 열 2개를 추가하고 싶다면 pad_width 매개변수에 (1, 2) 를 설정해주면 됩니다.


np.pad(x, (1, 2),
       mode='constant',
       constant_values=0)


array([[0, 0, 0, 0, 0, 0],
       [0, 1, 2, 3, 0, 0],
       [0, 7, 2, 5, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0]])

 



np.pad() 메소드를 사용하지 않고, 아래처럼 numpy.zeros() 로 원하는 형상(shape)대로 모두 '0'으로 채워진 배열을 먼저 만들어놓고, indexing을 사용하여 왼쪽, 위쪽, 오른쪽, 아래쪽에 행과 열을 1개씩 비워놓을 수 있는 위치에 기존 배열을 삽입하여 np.pad() 메소드를 사용했을 때와 동일한 결과를 얻을 수도 있습니다. 이때는 새로 만들어지는 배열 z의 형상(shape)과 기존 배열 x를 채워넣을 위치의 indexing에 신경을 써주어야 하므로 조금 신경이 쓰이는 편이기는 합니다. (위의 np.pad() 와 일처리 순서가 정 반대라고 생각하면 됩니다.)


z = np.zeros((4, 5))
print(z)

[[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]


z[1:-1, 1:-1] = x
print(z)
[[0. 0. 0. 0. 0.] [0. 1. 2. 3. 0.] [0. 7. 2. 5. 0.] [0. 0. 0. 0. 0.]]

 



np.pad() 함수에 몇가지 재미있는 옵션을 마저 소개하겠습니다.
constant_values 를 설정해주면 '0' 대신에 원하는 다른 특정 상수 값으로 빈 자리를 채워넣을 수 있습니다. '0' 대신에 '-1'을 한번 채워볼까요?


np.pad(x, (1, 1), constant_values=-1)


array([[-1, -1, -1, -1, -1],
       [-1,  1,  2,  3, -1],
       [-1,  7,  2,  5, -1],
       [-1, -1, -1, -1, -1]])




빈 자리 채워넣기하는 방법(mode)에는 항상 똑같은 상수('constant', default) 값을 채워넣는 방법 외에도 'edge', 'linear_ramp', 'maximum', 'mean', 'median', reflect', 'symmetric', 'wrap', 'empty' 등의 다양한 mode 옵션을 제공합니다. 이들 중에서 위의 예시에서 사용한 'constant' 이외에 'edge', 'maximum', 'wrap' 의 mode 옵션을 사용하여 채워넣기 padding을 해보겠습니다. (아래 결과에서 빨간색으로 표시한 부분이 padding된 부분입니다.)


  • mode = 'edge' : 가장 변두리의 원소 값으로 빈 곳 채우기


np.pad(x, (1, 1), mode='edge')


array([[1, 1, 2, 3, 3], [1, 1, 2, 3, 3], [7, 7, 2, 5, 5], [7, 7, 2, 5, 5]])

 


  • mode = 'maximum' : 행과 열의 가장 큰 값으로 빈 곳 채우기


np.pad(x, (1, 1), mode='maximum')


array([[7, 7, 2, 5, 7],
       [3, 1, 2, 3, 3],
       [7, 7, 2, 5, 7],
       [7, 7, 2, 5, 7]])



  • mode = 'wrap' : 행과 열의 반대편 끝에 있는 원소 값으로 빈 곳 채우기


np.pad(x, (1, 1), mode='wrap')


array([[5, 7, 2, 5, 7],
       [3, 1, 2, 3, 1],
       [5, 7, 2, 5, 7],
       [3, 1, 2, 3, 1]])


* Reference: https://numpy.org/doc/stable/reference/generated/numpy.pad.html



  2. 원소 개소가 다른 Ragged array를 특정 형상의 배열로 바꿀 때 빈자리를 '0'으로 채우기

     : tensorflow.keras.preprocessing.sequence.pad_sequence() 함수


위의 np.pad() 함수의 경우 변경하기 전의 원래 배열이 (m by n) 형상인 고정된 차원의 배열을 대상으로 채워넣기를 하였습니다. 두번째로 소개하려는 keras의 sequence.pad_sequence() 함수는 각 행의 원소 개수가 다른 Ragged array(?) 를 대상으로 특정 (j by k) 형상의 고정된 배열로 바꾸려고 할 때 빈 자리를 '0'으로 채워넣는데 사용할 수 있는 차이가 있습니다.


아래의 예를 보면 원소 개수가 1개, 2개, 3개, 4개로서 들쭉날쭉함을 알 수 있습니다. (list를 원소로 가지고 있고, data type 이 object 이네요.)



x2 = np.array([[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]])
display(x2)


array([list([1]), 
      list([2, 3]),
      list([4, 5, 6]),
      list([7, 8, 9, 10])], dtype=object)

 



TensorFlow와 Keras의 tf.keras.preprocessing.sequence() 메소드를 importing 해보겠습니다.



import tensorflow as tf
from tensorflow.keras.preprocessing import sequence

print(tf.__version__)

2.3.0

 



이제 Keras의 pad_sequences() 함수를 사용하여 가장 많은 원소를 가진 행에 맞추어서 (4 by 4) 형상의 배열로 바꾸고, 왼쪽의 빈자리는 '0'으로 채워넣기(padding)를 해보겠습니다.

tf.keras.preprocessing.sequence.pad_sequences(
    sequences, maxlen=None, dtype='int32',
    padding='pre', truncating='pre',
    value=0.0
)

이렇게 (4 by 4) 형상으로 해서 빈자리는 '0'으로 채워주고 나니 각 행의 원소 개수가 모두 4개로서 배열다운 배열이 되었습니다. padding을 해주는 위치의 기본 설정값은 padding='pre' 로서 앞쪽(왼쪽)에 '0'을 채워줍니다. (value=0 이 기본 설정값으로서 '0' 값으로 채워줌)


sequence.pad_sequences(x2) # default: padding='pre', value=0


array([[ 0,  0,  0,  1],
       [ 0,  0,  2,  3],
       [ 0,  4,  5,  6],
       [ 7,  8,  9, 10]], dtype=int32)

 



만약 padding을 해주는 위치를 뒤쪽(오른쪽)으로 하고 싶다면 padding='post' 로 매개변수 설정을 바꿔주면 됩니다.


sequence.pad_sequences(x2, padding='post')


array([[ 1,  0,  0,  0],
       [ 2,  3,  0,  0],
       [ 4,  5,  6,  0],
       [ 7,  8,  9, 10]], dtype=int32)

 



물론 빈 곳 채워넣기(padding)하는 값을 '0'이 아니라 다른 값으로 할 수도 있습니다. '-1'을 사용(value=-1)해서 앞쪽에 빈 곳을 채워넣기해보겠습니다.



sequence.pad_sequences(x2, padding='pre', value=-1)


array([[-1, -1, -1,  1],
       [-1, -1,  2,  3],
       [-1,  4,  5,  6],
       [ 7,  8,  9, 10]], dtype=int32)

 



maxlen 매개변수값을 별도로 설정해주지 않으면 배열 내 행 중에서 가장 많은 원소를 가진 행을 기준으로 maxlen 이 자동으로 정해지는데요, 이를 사용자가 직접 설정해줄 수도 있습니다. 아래의 예에서 maxlen=5 로 설정해주면 (4 by 5) 의 padding 된 배열이 생성됩니다.


sequence.pad_sequences(x2, padding='pre', value=0, maxlen=5)


array([[ 0,  0,  0,  0,  1],
       [ 0,  0,  0,  2,  3],
       [ 0,  0,  4,  5,  6],
       [ 0,  7,  8,  9, 10]], dtype=int32)




아래 예에서처럼 원래의 배열 x2 의 최대길이는 4인데 maxlen=3 으로 값을 설정하게 되면 4-3=1 개의 길이만큼의 원소 값들을 잘라내기(truncating) 해주어야 합니다. 이때 truncating='post' 라고 설정해주면 뒤쪽(오른쪽)을 기준으로 '1'개의 값들을 잘라내주고, 앞쪽을 기준으로 비어있는 곳에는 '0'의 값을 채워주게 됩니다.


sequence.pad_sequences(x2, padding='pre', value=0, maxlen=3, truncating='post')


array([[0, 0, 1],
       [0, 2, 3],
       [4, 5, 6],
       [7, 8, 9]], dtype=int32)



* Reference: https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/sequence/pad_sequences

많은 도움이 되었기를 바랍니다.
이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)

Posted by R Friend Rfriend

댓글을 달아 주세요

행렬의 값이 대부분 '0'인 행렬을 희소행렬(Sparse matrix) 이라고 하며, 반대로 행렬의 값이 대부분 '0이 아닌 값'을 가지는 경우 밀집행렬(Dense matrix) 혹은 조밀행렬이라고 합니다. 


가령, 자연어처리(NLP)에서 텍스트를 파싱해서 TF-IDF 행렬을 만들다보면 대부분의 값은 '0'으로 채워져 있고 '0'이 아닌 값은 듬성듬성 들어있는 희소행렬을 주로 얻게 됩니다. 


희소행렬(Sparse matrix)의 경우 대부분의 값이 '0'이므로 이를 그대로 사용할 경우 메모리 낭비가 심하고 또 연산시간도 오래 걸리는 단점이 있습니다. 이런 단점을 피하기 위해 희소행렬을 다른 형태의 자료구조로 변환해서 저장하고 사용합니다. 


희소행렬을 저장하는 자료구조 4가지에는 


(a) Dictionary of keys(DOK): key (행번호, 열번호) 대 value (데이터) 매핑

(b) List of lists (LIL): 링크드 리스트 알고리즘을 이용한 저장 기법, 내용의 추가와 삭제가 용이하지만 CSR 대비 메모리 낭비가 큼

(c) Coordinate list (COO): (행, 열, 값) 튜플 목록 저장

(d) Compressed sparse row (CSR): 가로의 순서대로 재정렬하는 방법으로 행에 관여하여 정리 압축


가 있습니다. 

* reference: https://en.wikipedia.org/wiki/Sparse_matrix



이중에서 Compressed Sparse Row(CSR) 형태(format) 자료구조의 장점과 단점을 표로 정리해보면 아래와 같습니다. 


 CSR 자료구조의 장점

(Advantages of the CSR format)

CSR 자료구조의 단점

(Disadvantages of the CSR format)

  •  효율적인 산술 연산
     (예: CSR + CSR, CSR * CSR 등)
  • 효율적인 행 슬라이싱
    (efficient row slicing)
  • 빠른 행렬 벡터 곱
    (fast matrix vector products)
  • 느린 열 슬라이싱
    (slow column slicing) 
    --> Compressed Sparse Column format 고려
  • 희소성 구조의 변화 시 연산비용 큼
    --> LIL, DOK 고려



이번 포스팅에서는 희소행렬에 대해 이들 중에서도 SciPy 모듈의 csr_matrix() 메소드를 사용하여 


(1) NumPy 희소행렬을 SciPy 압축 희소 행(CSR) 행렬로 변환하기

   (Converting a NumPy sparse matrix to a SciPy compressed sparse row matrix)


(2) SciPy 압축 희소 행(CSR) 행렬을 NumPy 희소행렬로 변환하기 

   (Converting a SciPy compressed sparse row matrix to a NumPy sparse matrix)


하는 각 2가지 방법을 소개하겠습니다. 






  (1) NumPy array 행렬을 SciPy 압축 희소 행(CSR) 행렬로 변환하기

      (Transforming a NumPy matrix to a SciPy compressed sparse row matrix)


NumPy array 데이터형의 희소행렬을 SciPy 압축 희소 행(CSR) 행렬 (compressed sparse row matrix)로 만드는 3가지 방법을 아래의 arr 넘파이 배열을 예로 들어서 설명해보겠습니다. 


class scipy.sparse.csr_matrix(arg1, shape=None, dtype=None, copy=False)


(1-1) arr 넘파이 배열에 대해 => scipy.sparse.csr_matrix(arr)

(1-2) 값 data, '0'이 아닌 원소의 열 위치 indices, 행 위치 시작 indptr 

        => csr_matrix((data, indices, indptr), shape=(5, 4))

(1-3) 값 data, '0'이 아닌 원소의 (행, 열) 위치 => csr_matrix((data, (row, col)), shape=(5, 4))




Compressed Sparse Row matrix로 변환할 대상이 되는 NumPy array 예제 행렬인 'arr' 을 먼저 만들어보겠습니다. 



import numpy as np

from scipy.sparse import csr_matrix


arr = np.array([[0, 1, 0, 2], 

                [0, 3, 4, 5], 

                [0, 0, 0, 0], 

                [6, 0, 0, 7], 

                [0, 8, 0, 0]])


arr

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]])



(1-1) arr 넘파이 배열에 대해 => scipy.sparse.csr_matrix(arr)


NumPy 배열 (rank-2 ndarray), 희소행렬, 밀집행렬을 scipy.sparse.csr)matrix() 메소드 안에 넣어주면 되니 제일 쉬운 방법입니다. 



# converting NumPy array into SciPy Compressed Sparse Row matrix

csr_mat = csr_matrix(arr)


csr_mat

[Out] <5x4 sparse matrix of type '<class 'numpy.longlong'>'
	with 8 stored elements in Compressed Sparse Row format>

 



위에서 만든 'csr_mat' 이름의 5x4 sparse matrix (CSR format) 에서 특성값(attributes)으로서 

  - (a) csr_mat.indptr : 행렬의 '0'이 아닌 원소의 행의 시작 위치

  - (b) csr_mat.indices : 행렬의 '0'이 아닌 원소의 열의 위치

  - (c) csr_mat.data : 행렬의 '0'이 아닌 원소 값



print('-- Compressed Sparse Row matrix --')

print('indptr:', csr_mat.indptr)

print('indices:', csr_mat.indices)

print('data:', csr_mat.data)


-- Compressed Sparse Row matrix --
indptr: [0 2 5 5 7 8]
indices: [1 3 1 2 3 0 3 1]
data: [1 2 3 4 5 6 7 8]

 



이를 그림으로 좀더 알기 쉽게 표현을 해보면 아래와 같습니다. 헷갈리지 않고 좀더 알아보기에 편리하도록 NumPy array 행렬의 값(data)을 숫자가 아니라 영어 알파벳으로 바꾸어서 표시하였습니다. 



SciPy Compressed Sparse Row matrix 에서 

  - data 는 행렬의 '0'이 아닌 원소 값이므로 이해하기 어려운게 없습니다. 

  - indices 도 행렬의 '0'이 아닌 원소의 위치 (row, column) 에서 열(column) 위치(index) 배열 [1, 3, 1, 2, 3, 0, 3, 1 ] 이므로 어려울게 없습니다. 

  - indptr 은 저는 처음에 봤을 때는 이게 뭔가하고 유심히 보면서 좀 고민을 했습니다. ^^;  indptr은 행을 기준으로 했을 때 행별로 '0'이 아닌 원소가 처음 시작하는 위치의 배열입니다. 말로 설명하기 좀 어려운데요, 가령 위의 NumPy 배열 'arr'의 '0'이 아닌 원소의 위치 (행 row, 열 col) 배열(위 그림의 중간에 표시되어 있음)을 보면, 

'arr' 배열의 첫번째 행 [0, a, 0, b] 는 '0'이 아닌 원소의 (row, col) 배열0 위치에서 시작, 

               두번째 행 [0, c, d, e] 는 '0'이 아닌 원소의 (row, col) 배열의 2 위치에서 시작, 

               세번째 행 [0, 0, 0, 0] 는 '0'이 아닌 원소의 (row, col) 배열의 5 위치에서 시작, (비어있음) 

               네번째 행 [f, 0, 0, g] 는 '0'이 아닌 원소의 (row, col) 배열의 5 위치에서 시작, 

                        (--> 왜냐하면, 세번째 행의 모든 값이 '0' 이므로 같은 위치인 5에서 시작함)

               다섯번째 행 [0, h, 0, 0] 는 '0'이 아닌 원소의 (row, col) 배열의 7 위치에서 시작, 

               마지막으로, 'arr' 의 원소의 개수 8 에서 끝남.  


이렇게 indptr을 이용하는 이유는 행 기준의 '0'이 아닌 원소의 (row, col) 을 사용하는 것보다 데이터를 좀더 압축할 수 (즉, 줄일 수) 있기 때문입니다. 위의 예의 경우 row 기준으로 '0'이 아닌 원소의 (row, col)에서 row만 보면 [0, 0, 1, 1, 1, 3, 3, 4] 로서 [0, 0], [1, 1, 1], [3, 3] 처럼 같은 행에 두 개 이상의 '0'이 아닌 원소가 있으면 같은 행 숫자가 반복됩니다. 이럴 때 indptr 을 사용하면 [0, 2, 5, 5, 7, 8] 처럼 행 기준으로 '0'이 아닌 원소가 시작되는 row 위치만 가져오면 되므로 저장해야하는 정보량을 줄일 수 (압축) 있게 됩니다.   



(1-2) 값 data, '0'이 아닌 원소의 열 위치 indices, 행 위치 시작 indptr 

        => csr_matrix((data, indices, indptr), shape=(5, 4))


NumPy array 행렬이 없더라도, data, indices, indptr 입력값과 output 행렬의 형상(shape) 을 알고 있다면 SciPy Compressed Sparse Row matrix를 아래처럼 만들 수 있습니다.

(다만, indptr, indices 를 사람이 직접 입력하기에는 좀 어려운 면이 있어서 위의 (1-1) 방법보다는 좀 어려워보이네요.)



# converting NumPy array into SciPy Compressed Sparse Row matrix

indptr = np.array([0, 2, 5, 5, 7, 8]) # the location of the first element of the row.

indices = np.array([1, 3, 1, 2, 3, 0, 3, 1]) # column indices

data = np.array([1, 2, 3, 4, 5, 6, 7, 8])    # corresponding value


csr_mat2 = csr_matrix((data, indices, indptr), shape=(5, 4))

csr_mat2

[Out] <5x4 sparse matrix of type '<class 'numpy.int64'>'
	with 8 stored elements in Compressed Sparse Row format>



print('-- Compressed Sparse Row matrix 2 --')

print('indptr:', csr_mat2.indptr)

print('indices:', csr_mat2.indices)

print('data:', csr_mat2.data)


-- Compressed Sparse Row matrix 2 --
indptr: [0 2 5 5 7 8]
indices: [1 3 1 2 3 0 3 1]
data: [1 2 3 4 5 6 7 8]

 




(1-3) 값 data, '0'이 아닌 원소의 (행, 열) => csr_matrix((data, (row, col)), shape=(5, 4))


세번째는 행렬에서 '0' 이 아닌 원소의 값(data)과 (행, 열) 위치 (row_ind, col_ind), 그리고 행렬의 형상(shape) 을 입력해주는 방식입니다. (사람 입장에서는 이 (1-3) 방식이 위의 (1-2) 방식보다는 직관적으로 이해하기가 더 쉽기는 합니다.)



# converting NumPy array into SciPy Compressed Sparse Row matrix

row = np.array([0, 0, 1, 1, 1, 3, 3, 4])

col = np.array([1, 3, 1, 2, 3, 0, 3, 1])

data = np.array([1, 2, 3, 4, 5, 6, 7, 8])


csr_mat3 = csr_matrix((data, (row, col)), shape=(5, 4))

csr_mat3

[Out] <5x4 sparse matrix of type '<class 'numpy.longlong'>'
	with 8 stored elements in Compressed Sparse Row format>

 

print('-- Compressed Sparse Row matrix 3 --')

print('indptr:', csr_mat3.indptr)

print('indices:', csr_mat3.indices)

print('data:', csr_mat3.data)


-- Compressed Sparse Row matrix 2 --
indptr: [0 2 4 4 6 7]
indices: [1 3 1 2 0 3 1]
data: [1 2 3 4 5 6 7]


-- Compressed Sparse Row matrix 3 --
indptr: [0 2 5 5 7 8]
indices: [1 3 1 2 3 0 3 1]
data: [1 2 3 4 5 6 7 8]





  (2) SciPy 압축 희소 행(CSR) 행렬을 NumPy 행렬로 변환하기

     (Transforming a SciPy compressed sparse row matrix into a NumPy matrix) 


SciPy 압축 희소 행 행렬을 NumPy 행렬로 변환하기는 아래 2가지 메소드를 이용하면 매우 쉽습니다. 


(2-1) scipy.sparse.csr_matrix.toarray() 메소드

(2-2) scipy.sparse.csr_matrix.todense() 메소드



위에서 만든 'csr_mat', 'csr_mat2', 'csr_mat3' 세 개의 압축 희소 행(CSR) 행렬을 아래에서 원래의 NumPy array 배열로 변환해보니 모두 동일하게 제대로 변환이 되었네요. 


(2-1) scipy.sparse.csr_matrix.toarray() 메소드



# converting sparse matrix to NumPy array

csr_mat.toarray()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)


csr_mat2.toarray()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)


csr_mat3.toarray()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)





(2-2) scipy.sparse.csr_matrix.todense() 메소드


SciPy Compressed Sparse Row matrix를 원래의 행렬로 변환할 때 그게 희소행렬(Sparse matrix) 일 수도 있고 아니면 밀집행렬(Dense matrix) 일 수도 있기 때문에 메소드 이름을 csr_matrix.todense() 라고 하면 좀 오해의 소지도 있어서 썩 잘 지은 메소드 이름은 아니라고 생각하는데요, 어쨌든 반환된 후의 결과는 위의 csr_matrix.toarray() 와 동일합니다. 



csr_mat.todense()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)

 




  (3) 동일 위치에 중복된 원소값은 합산 (Duplicate entries are summed together.)


아래의 행렬처럼 (row, column) 이 (0, 0)인 위치에 5, 3 의 값이 중복되어 있고, (1, 1)인 위치에 2, 4 의 값이 중복되어 있는 Compressed Sparse Row matrix 데이터는 중복된 위치의 값을 더해주게 됩니다. 


  5 + 3

 0

 0

 0

 2 + 4

 0

 0

 0

 0



# Duplicate entries are summed together. 

row = np.array([0, 1, 1, 0])

col = np.array([0, 1, 1, 0])

data = np.array([5, 2, 4, 3])

csr_matrix((data, (row, col)), shape=(3, 3)).toarray()


[Out]
array([[8, 0, 0],
       [0, 6, 0],
       [0, 0, 0]], dtype=int64)

 



[ Reference ]

* SciPy 모듈 sparse.csr_matrix() 메소드

  : https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html

* Sparse matrix: https://en.wikipedia.org/wiki/Sparse_matrix



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 값 배열에 가중치 배열을 곱해서 합한 가중합(weighted sum)을 구하는 3가지 방법을 소개하겠습니다. 


a 를 가중치, b 를 값 배열이라고 했을 때, 


(1) 내적을 이용한 가중합 계산: np.dot(a, b) or np.matmul(a, b)

(2) 브로드캐스팅(broadcasting)을 이용하여 가중치와 값을 원소끼리 곱한 후 합하는

     np.sum(a.reshape(5, 1) * b, axis=0)

(3) repeat()로 가중치를 값 배열 1축만큼 반복 생성한 후, 가중치와 값의 원소끼리 곱한 후 합하는, 

     np.sum(a.reshape(5, 1).repeat(3, axis=1) * b, axis=0)




먼저, numpy를 import하고, 예제로 사용할 가중치 배열 a와, 값의 행렬 b를 만들어보겠습니다. 



import numpy as np


# weights

a = np.array([0.5, 0.3, 0.1, 0.08, 0.02])


print('a shape:', a.shape)

a shape: (5,)


print(a)

[0.5  0.3  0.1  0.08 0.02]



# values

b = np.arange(15).reshape(5, 3)


print('b shape:', b.shape)

b shape: (5, 3)


print(b)

[[ 0  1  2]

 [ 3  4  5]

 [ 6  7  8]

 [ 9 10 11]

 [12 13 14]]

 




  (1) 내적을 이용한 가중합 계산: np.dot(a, b) 또는 np.matmul(a, b)


가장 편리한 방법은 np.dot() 또는 np.matmul() 메소드를 사용하여 내적(inner prodct, dot product)을 계산하는 것입니다. 이때 가중치 벡터 a 에 대해서는 형태 변환(reshape)을 할 필요가 없이 그대로 사용할 수 있습니다.  



np.dot(a, b)

Out[2]: array([2.46, 3.46, 4.46])


np.matmul(a, b)

Out[3]: array([2.46, 3.46, 4.46])

 




  (2) Broadcasting을 이용하여 가중치와 값을 원소끼리 곱한 후, axis=0으로 합하기


이번에는 위의 (1) 내적을 계산의 각 단계별로 분리해서 순서대로 해보겠습니다. 가중치 a와 값 b의 원소끼리 곱한 후에, axis=0을 기준으로 합할 것입니다. 


먼저, 가중치 a와 값 b를 원소끼리 곱하기 위해 가중치 a의 형태(shape)를 기존의 (5,)에서 a.reshape(5, 1) 을 적용하여 (5, 1) 의 형태로 변환을 해줍니다. 값이 들어있는 배열 b의 형태는 (5, 3) 이므로 가중치 배열 a의 (5, 1) 형태를 값 배열 b에 곱해주면 ==> 서로 형태가 같지 않으므로 numpy 는 가중치 a 배열 (5, 1) 을 (5, 3)으로 자동으로 형태 변환을 시켜서 값 배열 b 의 (5, 3) 형태와 동일하게 맞추어 주어 원소간 곱을 해줍니다. 이러한 기능을 브로드캐스팅(boradcasting) 이라고 합니다. 



# shape of a_rs and b are different

a_rs = a.reshape(5, 1)

print(a_rs.shape)

print(a_rs)

(5, 1)


print(b.shape)

(5, 3)


# multiply using boradcasting of a_rs

a_rs_b_mult = a_rs * b


print(a_rs_b_mult.shape)

(5, 3)


print(a_rs_b_mult)

[[0.   0.5  1.  ]

 [0.9  1.2  1.5 ]

 [0.6  0.7  0.8 ]

 [0.72 0.8  0.88]

 [0.24 0.26 0.28]]



# weighted sum

np.sum(a_rs_b_mult, axis=0)

Out[9]: array([2.46, 3.46, 4.46])



* numpy 배열들의 다른 차원의 배열 간 산술연산 시 Broadcasting 은 아래 포스팅을 참고하세요. 

https://rfriend.tistory.com/287




  (3) repeat()로 가중치를 반복 생성한 후, 가중치와 값을 원소끼리 곱한 후 합하기


위의 (2)번에서는 가중치 배열 a의 형태를 바꾼 후의 a_rs 배열과 값 b 배열을 곱할 때, 사람 눈에는 보이지않게 numpy가 알아서 자동으로 가중치 a_rs 배열 (5, 1) 형태를 브로드캐스팅(broadcasting)을 해주어서 (5, 3) 형태로 만들어서 원소끼리 곱해주었습니다. 




반면에, 이번 (3)번에서는 사람이 repeat(n, axis) 메소드를 사용해서 명시적으로 배열을 n번 만큼 axis 축을 기준으로 반복해주어서 (2)번의 브로드캐스팅의 역할을 수행해주는 것입니다. 


구현 관점에서 보면 브로드케스팅이 편리한 장점이 있고, 반면에 repeat() 메소드로 명시적으로 기입을 해주면 코딩하는 사람이 이해하기 쉬운 장점이 있습니다. 



# match the shape of a and b by repeatition 

a_rs_rp = a.reshape(5, 1).repeat(3, axis=1)


print(a_rs_rp.shape)

(5, 3)


print(a_rs_rp)

[[0.5  0.5  0.5 ]

 [0.3  0.3  0.3 ]

 [0.1  0.1  0.1 ]

 [0.08 0.08 0.08]

 [0.02 0.02 0.02]]



# multiplication of a_rs_rp and b per each elements

a_rs_rp_b_mult = a_rs_rp * b


print(a_rs_rp_b_mult.shape)

(5, 3)


print(a_rs_rp_b_mult)

[[0.   0.5  1.  ]

 [0.9  1.2  1.5 ]

 [0.6  0.7  0.8 ]

 [0.72 0.8  0.88]

 [0.24 0.26 0.28]]



# weighted sum

np.sum(a_rs_rp_b_mult, axis=0)

Out[17]: array([2.46, 3.46, 4.46])

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python numpy 모듈의 random.choice() 메소드를 사용하여 임의(무작위, 확률) 추출 (random sampling)하는 방법을 소개하겠습니다. 


numpy.random.choice() 메소드의 기본 Syntax는 아래와 같습니다. 각 parameter별로 예를 들어서 설명을 해보겠습니다. 


numpy.random.choice(a, size=None, replace=True, p=None)


이때 표본을 추출할 모집단에 해당하는 a 는 1-D array-like 또는 np.arange(n) 의 정수가 됩니다. 





  (1) 1부터 5까지의 정수 모집단에서 3개의 균등확률표본을 복원추출 하기

  (Generate a uniform random sample from np.arange(5) of size 3)


표본이 뽑힐 확률 p를 명시적으로 지정해주지 않으면 모든 원소가 뽑힐 확률이 동일한 (즉, p=1/N) 균등확률분포를 가정하고 표본이 추출됩니다. 

그리고, 복원추출(replacement)이 기본 설정값이므로 똑같은 값이 2번 이상 표본으로 뽑힐 수도 있습니다. 



import numpy as np


np.random.choice(5, 3) # with replacement

Out[3]: array([4, 0, 0])

 


참고로, 위의 np.random.choice(5, 3) 코드는 np.random.randint(0,5,3) 과 동일합니다. 



균등확률분포로 부터 임의 추출이므로 매번 표본으로 뽑히는 값이 바뀌게 됩니다. 위와 코드는 같지만 추출된 표본은 다르지요?



np.random.choice(5, 3) # sampled with different values

Out[4]: array([2, 0, 0])

 



복원추출을 하게 되면 1~5 의 정수를 가지는 모집단에서 5개를 초과하는 표본을 뽑는데 문제가 없습니다. 



np.random.choice(5, 10) # with replacement

Out[5]: array([2, 2, 4, 2, 4, 0, 0, 4, 3, 2])

 




 (2) 1~5의 정수 모집단에서 비균등 확률 p 로 3개 원소 임의 표본 복원추출 하기

    (Generate a non-uniform random sample from np.arange(5) of size 3:)


만약 모집단 내 각 원소별로 표본으로 뽑힐 확률 p를 알고 있다면, 혹은 명시적으로 지정을 해주고 싶다면 모수 p에 표본이 추출될 확률을 입력해주면 됩니다. 



p = [0.1, 0, 0.3, 0.6, 0]

np.random.choice(5, 3, p=p)

Out[7]: array([2, 3, 3])

 



만약 표본을 추출할 모집단(a)의 원소 개수(n)과 각 원소별 표본이 뽑힐 확률(p)의 원소 개수가 서로 다를 경우 ValueError 가 발생합니다. (아래 예에서는 확률 p에는 3개가, 모집단 a는 5개 원소로서 서로 다름)



# ValueError: 'a' and 'p' must have same size

p = [0.1, 0, 0.3]           # size 3

np.random.choice(5, 3, p=p) # size 5


ValueError: 'a' and 'p' must have same size

 



만약 모집단 원소별 표본으로 뽑힐 확률 p의 전체 합이 1 이 아니거나 0~1사이 값이 아닌 경우도 ValueError가 발생합니다. 왜냐하면, 확률의 정의 상 (1) 각 사건별 확률의 전체 합은 1이고, (2) 각 사건별 확률은 0~1 사이의 실수를 가져야 하기 때문입니다. 



# ValueError: probabilities do not sum to 1

p = [0.4, 0, 0.3, 0.6, 0]   # sum to 1.3 (not 1)

np.random.choice(5, 3, p=p)

ValueError: probabilities do not sum to 1



# ValueError: probabilities are not non-negative

p = [-0.4, 0, 0.3, 1.6, 0]   

np.random.choice(5, 3, p=p)


ValueError: probabilities are not non-negative





  (3) 1~5 정수 모집단에서 3개의 균등확률표본을 비복원추출(non-replacement) 하기

  (Generate a uniform random sample from np.arange(5) of size 3 

   without replacement)


옵션 중에서 replace=False 로 설정을 해주면 비복원추출(without replacement)을 합니다. 즉, 모집단에서 표본을 추출할 때 각 원소를 딱 한번만 추출하기 때문에 동일한 원소가 2번 이상 뽑히는 일은 없습니다. (default 옵션은 replace=True 임)



# Generate a uniform random sample from np.arange(5) of size 3 without replacement:

np.random.choice(5, 3, replace=False)

Out[11]: array([3, 2, 1])

 


참고로 위의 np.random.choice(5, 3, p=p) 코드는 np.random.permutation(np.arange(5))[:3] 과 동일합니다. 



비복원추출을 할 때는 한가지 조심해야 할 것이 있는데요, 모집단의 원소 개수보다 많은 수의 샘플을 비복원추출(replace=False)하려고 하면 ValueError가 발생합니다. (아래 예처럼, 5개의 원소를 가진 모집단에서 10개 표본을 비복원(즉, 중복 없이) 추출할 수는 없겠지요!)



# ValueError: Cannot take a larger sample than population when 'replace=False'

np.random.choice(5, 10, replace=False)


ValueError: Cannot take a larger sample than population when 'replace=False'

 



위의 (2)번에서 표본추출확률 p 를 설정하는 것과, 이번 (3)번의 비복원추출(replace=False)을 함께 설정해줌으로써 비균등확률(non-uniform random sample)로 비복원추출(without replacement) 샘플링도 물론 가능합니다. 



# Generate a non-uniform random sample from np.arange(5) of size 3 without replacement:

p = [0.1, 0, 0.3, 0.6, 0]

np.random.choice(5, 3, replace=False, p=p)

Out[13]: array([3, 0, 2])

 




  (4) 정수 대신에 임의의 배열처럼 생긴 객체의 원소를 확률추출하기

  (Any of the above can be repeated with an arbitrary array-like instead of just integers.)


np.random.choice(a, size=None, replace=True, p=None) 의 syntax에서 a 부분에 정수 배열 (np.arange(n)) 말고도 1-D array-like 객체를 대상으로 할 수도 있습니다. 아래 예에서는 과일 이름을 원소로 가지는 리스트로부터 비복원 비균등 확률 표본추출을 해보았습니다. 



# Any of the above can be repeated with an arbitrary array-like instead of just integers.

fruits = ['apple', 'banana', 'cherries', 'durian', 'grapes', 'lemon', 'mango']

p = [0.1, 0, 0.2, 0.5, 0.1, 0.05, 0.05]

np.random.choice(fruits, 3, p=p, replace=False)

Out[14]: array(['cherries', 'lemon', 'durian'], dtype='<U8')

 




  (5) 초기값 설정을 통한 재현가능성 확보

  (setting seed number for reproducibility)


np.random.seed() 로 초기값(seed value)을 설정해주면 매번 똑같은 확률표본을 추출할 수 있습니다. 만약 재현가능성(reproducibility)이 필요한 경우라면 초기값을 설정해주세요. 



# set seed number for reproducibility

np.random.seed(1004)

np.random.choice(5, 3)

Out[15]: array([2, 3, 3])


np.random.seed(1004)

np.random.choice(5, 3)

Out[16]: array([2, 3, 3])




참고로, 기계학습을 할 때 train set, test set 을 무작위로 분할하는 여러가지 방법은 https://rfriend.tistory.com/519 를 참고하시기 바랍니다. 


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


* Reference: https://docs.scipy.org/doc//numpy-1.10.4/reference/generated/numpy.random.choice.html



Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 A, B 두개의 값이 들어있는 배열과 위치를 지정해둔 idx 배열이 있다고 했을 때, A배열의 idx 배열 내 index 위치에다가 B배열의 원소를 순서대로 더하기하는 두가지 방법을 소개하겠습니다. 


(방법 1) for loop 으로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기

(방법 2) np.add.at(A, idx, B) 메소드로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기


for loop을 이용한 (방법 1)은 numpy의 add.at() 함수를 몰라도 어렵지 않게 구현할 수 있고 또 코드를 해석하기도 편한 장점이 있습니다만, 만약 더해야 하는 원소의 개수가 많아지면 for loop 연산을 하는데 시간이 많이 소요되는 단점이 있습니다. 


numpy의 add.at() 메소드를 사용하는 (방법 2)는 Vectorization 연산을 하여 한꺼번에 두 배열 간 idx 위치에 맞게 더하기 연산을 수행하므로 위의 for loop 방법 1 대비 빠르다는 장점이 있습니다. (더해야 하는 원소 개수가 작으면 방법1과 방법2의 속도 차이를 느끼기 힘든데요, 만약 몇 몇 백만개라면 for loop으로 수 분~ 수 시간 걸릴 것이 numpy.add.at() 메소드로는 수 초안에 끝낼 수도 있을만큼 성능 차이가 많이 날 것입니다. (대신 numpy.add.at() 함수를 기억하고 사용법도 알고 있어야 하겠지만요.)





  (방법 1) for loop 으로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기


먼저, 예제로 사용할 A배열, B배열, idx 배열을 만들어보겠습니다. 



import numpy as np


A = np.arange(24).reshape(8, 3)

print(A)

[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]
 [12 13 14]
 [15 16 17]
 [18 19 20]
 [21 22 23]]


B = np.arange(12).reshape(4, 3)

print(B)

[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]


idx = np.array([0, 0, 3, 5])




자, 이제 준비가 되었으니 for loop 문과 enumerate() 함수를 사용하여 A배열의 idx 배열 내 숫자의 위치에다가 B배열의 원소를 순서대로 더해보겠습니다. 

idx 가 array([0, 0, 2, 5]) 이므로 A배열의 1차원의 idx([0, 0, 3, 5]) 번째 위치(0, 0, 3, 5)의 원소에 B배열의 0, 1, 2, 3 번째 위치의 원소를 더하는 연산을 수행하게 됩니다. 


말로만 설명을 들으면 좀 헷갈리고 이해가 잘 안갈수도 있는데요, 아래에 배열 덧셈 연산식과 연산이 끝난 A배열에서 결과값이 바뀐 0, 2, 5 번째 원소를 빨간색으로 표시해두었으니 참고하기 바랍니다. (* idx([0, 0, 3, 5]) 에서 A[0]에 B[0]과 B[1]이 각 각 더해지는 것에 주의)


array A at idx([0])  + array B[0]          = array A[0]

array([0, 1, 2])        + array([0, 1, 2])    = array([0, 2, 4])


array A at idx([0])  + array B[1         = array A[0]

array([0, 2, 4])      + array([3, 4, 5])     = array([3, 6, 9])


array A at idx([3])  + array B[2]          = array A[3]

array([9, 10, 11])     + array([6, 7, 8])    = array([15, 17, 19])


array A at idx([5])  + array B[3]          = array A[5]

array([15, 16, 17])   + array([9, 10, 11])  = array([24, 25, 26])



for i, id in enumerate(idx):

    A[id] += B[i]


print(A)

[[ 3  6  9]
 [ 3  4  5]
 [ 6  7  8]
 [15 17 19]
 [12 13 14]
 [24 26 28]
 [18 19 20]
 [21 22 23]]

 




  (방법 2) np.add.at(A, idx, B) 함수로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기


위의 for loop을 이용한 (방법 1)과 똑같은 연산을 numpy의 add.at(A, idx, B) 메소드를 이용해서 수행하면 아래와 같습니다. 코드도 깔끔하고 연산속도도 더 빠르므로 특히 대용량 배열을 다루어야 하는 경우라면 알아두면 좋겠습니다. 



import numpy as np


A = np.arange(24).reshape(8, 3)

B = np.arange(12).reshape(4, 3)

idx = np.array([0, 0, 3, 5])


np.add.at(A, idx, B)


print(A)

[[ 3  6  9]
 [ 3  4  5]
 [ 6  7  8]
 [15 17 19]
 [12 13 14]
 [24 26 28]
 [18 19 20]
 [21 22 23]]

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^_^


Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python Numpy 배열 (array)에 차원을 추가하는 3가지 방법을 소개하겠습니다. 딥러닝 공부하다 보면 computer vision의 CNN에서 이미지 파일을 불러와서 다차원 배열로 변환할 때 사용하곤 합니다. 

1. numpy.reshape() 을 이용한 차원 추가

2. numpy.expand_dims() 을 이용한 차원 추가

3. numpy.newaxis 을 이용한 차원 추가


예제로 사용할 간단한 (4, 3, 2) 3차원의 다차원 배열을 만들어보겠습니다. 


import numpy as np

a = np.arange(24).reshape(4, 3, 2)

a

array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

       [[ 6,  7],
        [ 8,  9],
        [10, 11]],

       [[12, 13],
        [14, 15],
        [16, 17]],

       [[18, 19],
        [20, 21],
        [22, 23]]])

a.shape

(4, 3, 2)




(4, 3, 2) 차원의 배열 a에 차원을 추가하여 (1, 4, 3, 2)의 4차원 배열로 만들어보겠습니다. 


  1. numpy.reshape() 를 이용한 차원 추가


 

np.reshape(a, (1, 4, 3, 2))

array([[[[ 0,  1],
         [ 2,  3],
         [ 4,  5]],

        [[ 6,  7],
         [ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15],
         [16, 17]],

        [[18, 19],
         [20, 21],
         [22, 23]]]])


 np.reshape(a, ((1,) + a.shape))

array([[[[ 0,  1],
         [ 2,  3],
         [ 4,  5]],

        [[ 6,  7],
         [ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15],
         [16, 17]],

        [[18, 19],
         [20, 21],
         [22, 23]]]])

 a.reshape((1,) + a.shape)

array([[[[ 0,  1],
         [ 2,  3],
         [ 4,  5]],

        [[ 6,  7],
         [ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15],
         [16, 17]],

        [[18, 19],
         [20, 21],
         [22, 23]]]])



  2. numpy.expand_dims() 를 이용한 차원 추가



np.expand_dims(a, axis=0)

array([[[[ 0,  1],
         [ 2,  3],
         [ 4,  5]],

        [[ 6,  7],
         [ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15],
         [16, 17]],

        [[18, 19],
         [20, 21],
         [22, 23]]]])

 



  3. numpy.newaxis 를 이용한 차원 추가



a[:, np.newaxis]

array([[[[ 0,  1],
         [ 2,  3],
         [ 4,  5]]],


       [[[ 6,  7],
         [ 8,  9],
         [10, 11]]],


       [[[12, 13],
         [14, 15],
         [16, 17]]],


       [[[18, 19],
         [20, 21],
         [22, 23]]]])

 


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)

Posted by R Friend Rfriend

댓글을 달아 주세요

  1. 노래가 좋아 2020.05.19 15:12  댓글주소  수정/삭제  댓글쓰기

    잘 배우고 갑니다

이번 포스팅에서는 배열(array)에서 0보다 작은 수는 0으로 변환하고 나머지는 그대로 두는 여러가지 방법을 소개하겠습니다. 


1. List Comprehension with for loop

2. Indexing

3. np.where(condition[, x, y])

4. np.clip(a, a_min, a_max, out=None)





  1. List Comprehension: [0 if i < 0 else i for i in a]


아래처럼 for loop 을 써서 list comprehension 방법을 사용하면 특정 라이브러리의 함수를 사용하지 않아도 0보다 작은 수는 0으로 변환할 수 있습니다. 하지만, for loop 을 돌기 때문에 배열(array)가 커지면 성능이 문제될 수 있습니다.  원래의 배열 a는 그대로 있습니다. 



>>> import numpy as np

>>> a = np.arange(-5, 5)

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

>>> [0 if i < 0 else i for i in a]

[0, 0, 0, 0, 0, 0, 1, 2, 3, 4]

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])





  2. Indexing: a[a < 0] = 0


아래처럼 indexing을 사용해서 a[a < 0] = 0 처럼 0보다 작은 값이 위치한 곳에 0을 직접 할당할 수 있습니다. 이렇게 하면 원래의 배열 a가 변경됩니다. 



>>> a = np.arange(-5, 5)

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

>>> a[a < 0] = 0

>>> a

array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4])

 




  3. np.where() : np.where(a < 0, 0, a)


np.where(조건, True일 때 값, False일 때 값) 를 사용하면 편리하게 0보다 작은 조건의 위치에 0을 할당할 수 있습니다. 벡터 연산을 하므로 for loop이 돌지 않아서 속도가 매우 빠릅니다. 원래의 배열 a는 변경되지 않고 그대로 있습니다. 



>>> a = np.arange(-5, 5)

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

>>> np.where(a < 0, 0, a)

array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4])

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

 



만약 0보다 작은 수는 0으로 변환, 2보다 큰 수는 2로 변환하고 싶다면 아래처럼 np.where() 안에 np.where()를 한번 더 넣어서 써주면 되는데요, 코드가 좀 복잡해보입니다. 



>>> a = np.arange(-5, 5)

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

>>>

>>> np.where(a < 0, 0, np.where(a > 2, 2, a))

array([0, 0, 0, 0, 0, 0, 1, 2, 2, 2])

 




  4. np.clip() : np.clip(a, 0, 4, out=a)


np.clip(배열, 최소값 기준, 최대값 기준) 을 사용하면 최소값과 최대값 조건으로 값을 기준으로 해서, 이 범위 기준을 벗어나는 값에 대해서는 일괄적으로 최소값, 최대값으로 대치해줄 때 매우 편리합니다. 최소값 부분을 0으로 해주었으므로 0보다 작은 값은 모두 0으로 대치되었습니다. 이때 원래의 배열 a는 그대로 있습니다. 



>>> a = np.arange(-5, 5)

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

>>> np.clip(a, 0, 4)

array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4])

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

 



np.clip(배열, 최소값 기준, 최대값 기준, out 배열)을 사용해서 out = a 를 추가로 설정해주면 반환되는 값을 배열 a에 저장할 수 있습니다. 배열 a의 0보다 작았던 부분이 모두 0으로 대치되어 a가 변경되었음을 확인할 수 있습니다. 



>>> np.clip(a, 0, 4, out=a)

array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4])

>>> a

array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4])

 



최소값 기준만 적용해서 간단하게 '0'보다 작은 수는 모두 0으로 바꾸는 것은 a.clip(0) 처럼 메소드를 사용해도 됩니다. 



>>> a = np.arange(-5, 5)

>>> a

array([-5, -4, -3, -2, -1,  0,  1,  2,  3,  4])

>>> a.clip(0)

array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4])

 



많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^^



Posted by R Friend Rfriend

댓글을 달아 주세요

학교 다닐 때 행렬로 연립방정식 풀었던 기억이 날 듯 합니다. 선형대수(Linear Algebra)는 통계, 기계학습, 공학, 영상/이미지 처리 등 여러 분야에서 활용이 됩니다. 선형대수를 전부 다루려면 너무나 방대하므로, 이번 포스팅에서는 Python의 NumPy에 있는 선형대수(Linear Algebra) 함수들 중에서 자주 사용하는 함수에 대해서만 선별적으로 소개하겠습니다. 그리고 선형대수의 이론적인 부분은 별도로 참고할 수 있는 링크를 달도록 하겠습니다. 


  • 단위행렬 (Unit matrix): np.eye(n)
  • 대각행렬 (Diagonal matrix): np.diag(x)
  • 내적 (Dot product, Inner product): np.dot(a, b)
  • 대각합 (Trace): np.trace(x)
  • 행렬식 (Matrix Determinant): np.linalg.det(x)
  • 역행렬 (Inverse of a matrix): np.linalg.inv(x)
  • 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)
  • 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)
  • 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)
  • 최소자승 해 풀기 (Compute the Least-squares solution): m, c = np.linalg.lstsq(A, y, rcond=None)[0]




 1. 단위행렬 혹은 항등행렬 (Unit matrix, Identity matrix): np.eye(n)


단위행렬은 대각원소가 1이고, 나머지는 모두 0인 n차 정방행렬을 말하며, numpy의 eye() 함수를 사용해서 만들 수 있습니다. 


* 참고 링크 : https://rfriend.tistory.com/141



import numpy as np


unit_mat_4 = np.eye(4)


print(unit_mat_4)

[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

 




  1. 대각행렬 (Diagonal matrix): np.diag(x)


대각행렬은 대각성분 이외의 모든 성분이 모두 '0'인 n차 정방행렬을 말합니다. 아래 예시의 행렬에서 빨간색으로 표시한 원소를 '0'으로 바꾼 행렬이 대각행렬입니다. 


* 참고 링크 : http://rfriend.tistory.com/141


 

In [1]: import numpy as np


In [2]: x = np.arange(9).reshape(3, 3)


In [3]: print(x)

[[0 1 2]

 [3 4 5]

 [6 7 8]]


In [4]: np.diag(x)

Out[4]: array([0, 4, 8])


In [5]: np.diag(np.diag(x))

Out[5]:

array([[0, 0, 0],

        [0, 4, 0],

        [0, 0, 8]])





  2. 내적 (Dot product, Inner product): np.dot(a, b), a.dot(b)


matrix dot product, inner product, scalar product, projection product

Python에서 '*' 를 사용한 두 행렬 간 곱은 원소 간 곱(element-wise product)을 반환하며, 선형대수에서 말하는 행렬 간 내적 곱을 위해서는 np.dot() 함수를 이용해야 합니다. 


원소 간 곱 (element-wise product)

: a*b

내적 (dot product, inner product)

: np.dot(a, b)

 

In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [8]: a*a

Out[8]:

array([[0, 1],

        [4, 9]])


In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [9]: np.dot(a, a)

Out[9]:

array([[ 2, 3],

        [ 6, 11]])



np.dot(a, b) NumPy 함수와 a.dot(b)의 배열 메소드의 결과는 동일합니다. 



In [10]: a.dot(a)

Out[10]:

array([[ 2, 3],

        [ 6, 11]])

 




  3. 대각합 (Trace): np.trace(x)


정방행렬의 대각에 위치한 원소를 전부 더해줍니다. 

아래의 2차 정방행렬 예의 대각합은 0+5+10+15 = 30 이 됩니다. (파란색으로 표시함)



In [12]: b = np.arange(16).reshape(4, 4)


In [13]: print(b)

[[ 0 1 2 3]

 [ 4 5 6 7]

 [ 8 9 10 11]

 [12 13 14 15]]


In [14]: np.trace(b)

Out[14]: 30

 



3차원 행렬에 대해서도 대각합을 구할 수 있습니다. 2차원은 대각선 부분의 원소 값을 전부 더하면 되지만 3차원 행렬에서는 대각(diagonal)이 어떻게 되나 좀 헷갈릴 수 있겠습니다. 아래의 3차원 행렬의 대각합을 구하는 예를 살펴보면, [0+12+24, 1+13+25, 2+14+26] = [36, 39, 42] 가 됩니다. 



In [15]: c = np.arange(27).reshape(3, 3, 3)


In [16]: print(c)

[[[ 0 1 2]

  [ 3 4 5]

  [ 6 7 8]]


 [[ 9 10 11]

  [12 13 14]

  [15 16 17]]


 [[18 19 20]

  [21 22 23]

  [24 25 26]]]


In [17]: np.trace(c)

Out[17]: array([36, 39, 42])

 




  4. 행렬식 (Matrix Determinant): np.linalg.det(x)


역행렬이 존재하는지 여부를 확인하는 방법으로 행렬식(determinant, 줄여서 det)이라는 지표를 사용합니다. 이 행렬식이 '0'이 아니면 역행렬이 존재하고, 이 행렬식이 '0'이면 역행렬이 존재하지 않습니다. 


* 참고 링크 : http://rfriend.tistory.com/142


아래의 예에서 array([[1, 2], [3, 4]]) 의 행렬식이 '-2.0'으로서, '0'이 아니므로 역행렬이 존재한다고 판단할 수 있습니다. 



In [18]: d = np.array([[1, 2], [3, 4]])


In [19]: np.linalg.det(a)

Out[19]: -2.0

 




  5. 역행렬 (Inverse of a matrix): np.linalg.inv(x)


역행렬은 n차정방행렬 Amn과의 곱이 항등행렬 또는 단위행렬 In이 되는 n차정방행렬을 말합니다. A*B 와 B*A 모두 순서에 상관없이 곱했을 때 단위행렬이 나오는 n차정방행렬이 있다면 역행렬이 존재하는 것입니다.

역행렬은 가우스 소거법(Gauss-Jordan elimination method), 혹은 여인수(cofactor method)로 풀 수 있습니다. 




In [20]: a = np.array(range(4)).reshape(2, 2)


In [21]: print(a)

[[0 1]

 [2 3]]


In [22]: a_inv = np.linalg.inv(a)


In [23]: a_inv

Out[23]:

array([[-1.5, 0.5],

        [ 1. , 0. ]])




위의 예제에서 np.linalg.inv() 함수를 사용하여 푼 역행렬이 제대로 푼 것인지 확인을 해보겠습니다. 역행렬의 정의에 따라서 원래의 행렬에 역행렬을 곱하면, 즉, a.dot(a_inv) 또는 np.dot(a, a_inv) 를 하면 단위행렬(unit matrix)가 되는지 확인해보겠습니다. 



In [24]: a.dot(a_inv)

Out[24]:

array([[1., 0.],

         [0., 1.]])

 




  6. 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)


정방행렬 A에 대하여 Ax = λx  (상수 λ) 가 성립하는 0이 아닌 벡터 x가 존재할 때 상수 λ 를 행렬 A의 고유값 (eigenvalue), x 를 이에 대응하는 고유벡터 (eigenvector) 라고 합니다. 


np.linalg.eig() 함수는 고유값(eigenvalue) w, 고유벡터(eigenvector) v 의 두 개의 객체를 반환합니다. 


In [25]: e = np.array([[4, 2],[3, 5]])


In [26]: print(e)

[[4 2]

[3 5]]


In [27]: w, v = np.linalg.eig(e)


#  w: the eigenvalues lambda

In [28]: print(w)

[2. 7.]


# v: the corresponding eigenvectors, one eigenvector per column

In [29]: print(v)

[[-0.70710678 -0.5547002 ]

[ 0.70710678 -0.83205029]]

 



고유벡터는 배열 인덱싱하는 방법을 사용해서 각 고유값에 대응하는 고유벡터를 선택할 수 있습니다. 



# eigenvector of eigenvalue lambda 2

In [30]: print(v[:, 0]

[-0.70710678 0.70710678]


# eigenvector of eigenvalue labmda 7

In [31]: print(v[:, 1]

[-0.5547002 -0.83205029]

 




  7. 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)


특이값 분해는 고유값 분해(eigen decomposition)처럼 행렬을 대각화하는 한 방법으로서, 정방행렬뿐만 아니라 모든 m x n 행렬에 대해 적용 가능합니다. 특이값 분해는 차원축소, 데이터 압축 등에 사용할 수 있습니다. 이론적인 부분은 설명하자면 너무 길기 때문에 이 포스팅에서는 설명하지 않겠으며, 아래의 링크를 참고하시기 바랍니다. 


* 참고 링크 : http://rfriend.tistory.com/185


아래의 np.linalg.svd(A) 예제는 위의 참고 링크에서 사용했던 예제와 동일한 것을 사용하였습니다. 


In [32]: A = np.array([[3,6], [2,3], [0,0], [0,0]])


In [33]: print(A)

[[3 6]

 [2 3]

 [0 0]

 [0 0]]


In [34]: u, s, vh = np.linalg.svd(A)


In [35]: print(u)

[[-0.8816746 -0.47185793 0. 0. ]

 [-0.47185793 0.8816746 0. 0. ]

 [ 0. 0. 1. 0. ]

 [ 0. 0. 0. 1. ]]


In [36]: print(s)

[7.60555128 0.39444872]


In [37]: print(vh)

[[-0.47185793 -0.8816746 ]

 [ 0.8816746 -0.47185793]]

 




  8. 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)


아래의 두 개 연립방정식의 해(x0, x1)를 np.linalg.solve(a, b) 함수를 사용하여 풀어보겠습니다. 



위의 연립방정식을 어떻게 행렬로 입력하고, np.linalg.solve(a, b)에 입력하는지 유심히 살펴보시기 바랍니다. 



In [38]: a = np.array([[4, 3], [3, 2]])


In [39]: b = np.array([23, 16])


In [40]: x = np.linalg.solve(a, b)


In [41]: print(x)

[2. 5.]

 



NumPy 가 제대로 x0, x1의 해를 풀었는지 확인해보겠습니다. x0=2, x1=5 가 해 맞네요!



In [42]: np.allclose(np.dot(a, x), b)

Out[42]: True

 




  9. 최소자승 해 풀기 (Compute the Least-squares solution)

     : m, c = np.linalg.lstsq(A, y, rcond=None)[0]


회귀모형 적합할 때 최소자승법(Least-squares method)으로 잔차 제곱합을 최소화하는 회귀계수를 추정합니다. 


* 참고 링크 : https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html



아래의 예에서는 회귀계수 m, y절편 c를 최소자승법을 사용해서 구해보겠습니다. 



In [43]: x = np.array([0, 1, 2, 3])


In [44]: y = np.array([-1, 0.2, 0.9, 2.1])


In [45]: A = np.vstack([x, np.ones(len(x))]).T


In [46]: A

Out[46]:

array([[0., 1.],

        [1., 1.],

        [2., 1.],

        [3., 1.]])


In [47]: m, c = np.linalg.lstsq(A, y, rcond=None)[0]


In [48]: print(m, c)

0.9999999999999999 -0.9499999999999997

 



아래의 그래프에서 점은 원래의 데이터이며, 빨간색 선은 최소자승법으로 추정한 회귀식의 적합선이 되겠습니다. 



In [49]: import matplotlib.pyplot as plt

    ...: plt.plot(x, y, 'o', label='Original data', markersize=10)

    ...: plt.plot(x, m*x + c, 'r', label='Fitted line')

    ...: plt.legend()

    ...: plt.show()




많은 도움이 되었기를 바랍니다. 


Posted by R Friend Rfriend

댓글을 달아 주세요