머신러닝/numpy

NumPy의 기초(5)

빈코더 2021. 10. 6. 13:08
728x90

집한 관련 함수

NumPy는 1차원 ndarray를 위한 몇가지 기본적인 집합 연산을 제공한다.

아마도 가장 자주 사용하는 함수는 배열 내에서 중복된 원소를 제거하고 남은 원소를 정렬된 형태로 반환하는

np.unique일 것이다.

in  : names = np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
      np.unique(names)
out : array(['Bob', 'Joe', 'Will'], dtype='<U4')

in  : ints = np.array([3,3,3,2,2,1,1,4,4])
      np.unique(ints)
out : array([1, 2, 3, 4])

np.in1d 함수는 두 개읜 배열을 인자로 받아서 첫 번째 배열의 원소가 두 번째 배열의 원소를 포함하는지

나타내는 불리언 배열을 반환한다.

in  : values = np.array([6,0,0,3,2,5,6])
      np.in1d(values,[2,3,6])
out : array([ True, False, False,  True,  True, False,  True])
배열 집합 연산
  • unique(x): 배열 x에서 중복된 원소를 제거한 뒤 정렬하여 반환한다.
  • intersect1d(x,y): 배열 x와 y에 공통적으로 존재하는 원소를 정렬하여 반환한다.
  • union1d(x,y): 두 배열의 합집합을 반환한다.
  • in1d(x,y): x의 원소가 y의 원소에 포함되는지 나타내는 불리언 배열을 반환한다.
  • setdiff1d(x,y): x와 y의 차집합을 반환한다.
  • setxor1d(x,y): 한 배열에는 포함되지만 두 배열 모두에는 포함되지 않는 원소들의 집합인 대칭 차집합을 반환한다.

배열 데이터의 파일 입출력

NumPy는 디스크에서 텍스트나 바이너리 형식의 데이터를 불러오거나 저장할 수 있다.

np.save와 np.load는 배열 데이터를 효과적으로 디스크에 저장하고 불러오기 위한 함수이다.

배열은 기본적으로 압축되지 않은 원시 바이너리 형식의 .npy파일로 저장된다.

in  : arr = np.arange(10)
      np.save('some_array', arr) 

저장되는 파일 경로가 .npy로 끝나지 않으면 자동적으로 확장자가 추가된다.

이렇게 저장된 배열은 np.load로 불러올 수 있다.

in  : np.load('some_array.npy')
out : array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

np.savez함수를 이용하면 여러 개의 배열을 압축된 형식으로 저장할 수 있는데,

저장하려는 배열을 키워드 인자 형태로 전달한다.

in  : np.savez('array_archive.npz', a=arr, b=arr)

in  : arch = np.load('array_archive.npz')
      arch['b']
out : array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

압축이 잘되는 형식의 데이터라면 대신 numpy.savez_compressed를 사용하자.

in  : np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)
      arch2 = np.load('arrays_compressed.npz')
      arch2['b'] 
out : array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

선형대수

행렬의 곱셉, 분할, 행렬식 그리고 정사각 행렬 수학 같은 선형대수는 배열을 다루는 라이브러리에서 중요한 부분이다.

매트랩 같은 언어와 다르게 두 개의 2차원 배열을 * 연산자로 곱하면 행렬 곱셈이 아니라 대응하는 각각의 원소의 곱을 계산한다.

행렬 곱셈은 배열 메서드이자 numpy 네임스페이스 안에 있는 dot함수를 이용해서 계산하자.

in  : x = np.array([[1,2,3],[4,5,6]])
      y = np.array([[6,23],[-1,7],[8,9]])
      x, y
out : (array([[1, 2, 3],
              [4, 5, 6]]),
       array([[ 6, 23],
              [-1,  7],
              [ 8,  9]]))

in  : x.dot(y), np.dot(x,y) # 2개는 비슷하다.
out : (array([[ 28,  64],
              [ 67, 181]]),
       array([[ 28,  64],
              [ 67, 181]])) 

2차원 배열과 곱셈이 가능한 크기의 1차원 배열 간의 행렬 곱셈의 결과는 1차원 배열이다.

in  : np.dot(x,np.ones(3))
out : array([ 6., 15.])

파이썬 3.5부터 사용할 수 있는 @ 기호는 행렬 곱셈을 수행하는 연산자이다.

in  : x @ np.ones(3)
out : array([ 6., 15.])

numpy.linalg는 행렬의 분할과 역행렬, 행렬식과 같은 것들을 포함하고 있다. 이는 매트랩,R 같은 언어에서

사용하는 표준 포트란 라이브러리인 BLAS,LAPACK 또는 Intel MKL을 사용해서 구현되었다.

in  : from numpy.linalg import inv, qr
      X = np.random.randn(5,5)
      mat = X.T.dot(X)
      inv(mat)
out : array([[  1.65791817,   0.19102552,  -0.915723  ,  -2.14627913,
                0.6529658 ],
             [  0.19102552,   3.3224154 ,   6.75404589,   1.42767562,
               -3.99553952],
             [ -0.915723  ,   6.75404589,  21.12846251,   2.26406894,
              -12.95301012],
             [ -2.14627913,   1.42767562,   2.26406894,   5.14040658,
               -1.46406556],
             [  0.6529658 ,  -3.99553952, -12.95301012,  -1.46406556,
                8.12964249]])

in  : mat.dot(inv(mat))
out : array([[ 1.00000000e+00,  9.73048263e-16,  1.21558413e-15,
               4.08989193e-15, -1.71996985e-15],
             [ 5.32083634e-16,  1.00000000e+00, -5.67853323e-15,
              -1.88045345e-15,  5.81422861e-16],
             [-6.85761722e-17,  2.23606874e-15,  1.00000000e+00,
               2.11757715e-15, -1.61640160e-15],
             [-9.11894568e-16, -5.09299941e-16,  1.38603777e-15,
               1.00000000e+00,  2.77108106e-16],
             [ 4.54152491e-17,  1.91403645e-15,  1.91132879e-14,
               1.88244552e-15,  1.00000000e+00]])

in  : q, r = qr(mat)

in  : q
out : array([[-0.67427654, -0.58420058, -0.24317864,  0.37846503,  0.0410999 ],
             [ 0.52675722, -0.40161203, -0.69816207, -0.10274056, -0.25149295],
             [-0.29587335,  0.4412488 , -0.20013331,  0.11392924, -0.81530684],
             [-0.36114481, -0.1784972 , -0.00260157, -0.91061198, -0.0921533 ],
             [-0.22340852,  0.520438  , -0.64294149, -0.06336044,  0.51170755]])

in  : r
out : array([[-8.54016321,  7.75478498, -5.97149341, -4.84500967, -5.91719138],
             [ 0.        , -1.25004494,  3.60995808,  0.21493492,  5.2401242 ],
             [ 0.        ,  0.        , -2.50559991, -0.0595403 , -4.08199664],
             [ 0.        ,  0.        ,  0.        , -0.18906522, -0.04184247],
             [ 0.        ,  0.        ,  0.        ,  0.        ,  0.06294343]])

X.T.dot(X)은 X.T의 전치행렬과 X의 곱을 계산한다.

자주 사용하는 선형대수 함수 모음
  • diag: 정사각 행렬의 대각/비대각 원소를 1차원 배열로 반환하거나,
    1차원 배열을 대각선 원소로 하고 나머지는 0으로 채운 단위 행렬을 반환한다.
  • dot: 행렬 곱셈
  • trace: 행렬의 대각선 원소의 합을 계산
  • det: 행렬식을 계산한다.
  • eig: 정사각 행렬의 고윳값과 고유벡터를 계산한다.
  • inv: 정사각 행렬의 역행렬을 계산한다.
  • pinv: 정사각 행렬의 무어-펜로즈 유사역원 역행렬을 구한다.
  • qr: QR분해를 계산한다.
  • svd: 특잇값 분해(SVD)를 계산한다.
  • solve: A가 정사각 행렬일 때 Ax = B를 만족하는 x를 구한다.
  • lstsq: Ax = b를 만족하는 최소제곱해를 구한다.

난수 생성

numpy.random 모듈은 파이썬 내장 random 함수를 보강하여 다양한 종류의 확률분포로부터

효과적으로 표본값을 생성하는 데 주로 사용된다.

예를 들어 normal을 사용하여 표준정규분포로 부터 4x4 크기의 표본을 생성할 수 있다.

in  : samples = np.random.normal(size=(4,4))
      samples
out : array([[ 0.27736344, -0.74937387, -0.83935769,  1.068788  ],
             [-1.30344171, -0.44795558,  1.24656545, -1.720645  ],
             [ 0.40024253,  0.75544651,  2.04030529, -0.1969776 ],
             [ 0.68205537, -0.06302825,  0.15166983, -0.6813747 ]])

이에 비해 파이썬 내장 random 모듈은 한 번에 하나의 값만 생성할 수 있다.

다음 성능 비교에서 알 수 있듯이 numpy.random은 매우 큰 표본을 생성하는데 파이썬 내장 모듈보다 수십 배 이상 빠르다.

in  : from random import normalvariate
      n = 1000000
      %timeit samples = [normalvariate(0,1) for _ in range(n)]
out : 998 ms ± 23.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

in  : %timeit np.random.normal(size=n)
out : 31.4 ms ± 2.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

이를 엄밀하게는 유사 난수라고 부르는데, 난수 생성기의 시드값에 따라 정해진 난수를 알고리즘으로 생성하기 때문이다.

NumPy의 난수 생성기의 시드값은 np.random.seed를 이용해서 변경할 수 있다.

in  : np.random.seed(1234)

numpy.random에서 제공하는 데이터를 생성할 수 있는 함수들은 전역 난수 시드값을 이용한다.

numpy.random.RandomState를 이용해서 다른 난수 생성기로부터 격리된 난수 생성기를 만들수 있다.

in  : rng = np.random.RandomState(1234)
      rng.randn(10)
out : array([ 0.47143516, -1.19097569,  1.43270697, -0.3126519 , -0.72058873,
                 0.88716294,  0.85958841, -0.6365235 ,  0.01569637, -2.24268495])
numpy.random함수
  • seed: 난수 생성기의 시드를 지정한다.
  • permutation: 순서를 임의로 바꾸거나 임의의 순열을 반환한다.
  • shuffle: 리스트나 배열의 순서를 뒤섞는다.
  • rand: 균등분포에서 표본을 추출한다.
  • randint: 주어진 최소/최대 범위 안에서 임의의 난수를 추출한다.
  • randn: 표준편차가 1이고 평균값이 0인 정규분포(매트랩과 같은 방식)에서 표본을 추출한다.
  • binomial: 이항분포에서 표본을 추출한다.
  • normal: 정규분포(가우시안)에서 표본을 추출한다.
  • beta: 베타분포에서 표본을 추출한다.
  • chisquare: 카이제곱분포에서 표본을 추출한다.
  • gamma: 감마분포에서 표본을 추출한다.
  • uniform: 균등(0,1)분포에서 표본을 추출한다.
계산 오르내리기 예제

계단 오르내리기 예제는 배열 연산의 활용을 보여줄 수 있는 간단한 애플리케이션이다.
계단 중간에서 같은 확률로 한 계단 올라가거나 내려간다가 가정하자.

in  : # 순수 파이썬 코드
      import random
      position = 0
      walk = [position]
      steps = 1000
      for i in range(steps):
          step = 1 if random.randint(0,1) else -1
          position += step
          walk.append(position)

      plt.plot(walk[:100])
out : [<matplotlib.lines.Line2D at 0x20d6a191400>] # 계단 모양 그래프가 나온다.

walk는 계단을 오르거나 내려간 값의 누적합이라는 사실을 알 수 있으면 배열식으로 나타낼 수 있다.

그래서 np.random 모듈을 사용해서 1000번 수행한 결과(1, -1)를 한번에 저장하고 누적합을 계산한다.


수치는 다 다르기 때문에 다르다고 틀린거 아니냐고 오해하지 말기!

in  : nsteps = 1000
      draws = np.random.randint(0, 2, size=nsteps)
      steps = np.where(draws > 0, 1, -1)
      walk = steps.cumsum()

in  : walk.min()
out : -9

in  : walk.max()
out : 60

in  : walk
out : array([ 1,  2,  1,  2,  1,  0, -1,  0,  1,  2,  3,  4,  3,  2,  3,  2,  1,
              0, -1, -2, -3, -4, -5, -6, -5, -6, -5, -4, -5, -6, -5, -6, -7, -6,
             -7, -6, -7, -8, -9, -8, -7, -6, -7, -6, -5, -6, -5, -6, -5, -6, -5,
             -4, -3, -2, -3, -2, -3, -2, -1, -2, -3, -2, -3, -4, -3, -2, -1, -2,
             -3, -4, -3, -2, -1,  0,  1,  2,  3,  2,  3,  2,  3,  2,  3,  2,  1,
              0, -1, -2, -3, -4, -5, -4, -3, -2, -3, -2, -3, -4, -3, -2, -3, -2,
             -3, -2, -1, -2, -1, -2, -3, -4, -3, -2, -3, -4, -5, -6, -7, -6, -5,
             -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4,
             -3, -2, -1,  0, -1,  0,  1,  2,  1,  0, -1,  0, -1,  0, -1,  0,  1,
              0,  1,  0, -1,  0,  1,  2,  3,  2,  1,  0,  1,  0, -1, -2, -3, -4,
             -5, -6, -5, -6, -7, -6, -5, -6, -7, -8, -7, -8, -9, -8, -7, -8, -9,
             -8, -7, -8, -9, -8, -7, -8, -9, -8, -7, -8, -7, -8, -9, -8, -7, -6,
             -7, -8, -7, -6, -5, -6, -7, -8, -7, -6, -7, -8, -7, -6, -5, -6, -5,
             -4, -3, -2, -1,  0, -1, -2, -1, -2, -1,  0, -1,  0,  1,  0, -1, -2,
             -1, -2, -1, -2, -3, -4, -5, -6, -5, -4, -3, -2, -1, -2, -1,  0,  1,
              2,  1,  2,  3,  4,  5,  6,  5,  6,  7,  6,  7,  6,  5,  4,  3,  4,
              5,  6,  7,  8,  7,  8,  7,  6,  5,  6,  7,  8,  7,  6,  7,  8,  9,
              8,  7,  8,  7,  6,  7,  8,  9, 10, 11, 10, 11, 12, 11, 10, 11, 10,
              9, 10, 11, 10, 11, 12, 11, 12, 13, 12, 11, 12, 13, 12, 13, 14, 13,
             14, 15, 16, 15, 14, 13, 14, 15, 16, 15, 16, 17, 16, 15, 14, 15, 14,
             13, 14, 15, 14, 13, 12, 13, 14, 15, 14, 15, 16, 15, 16, 15, 16, 17,
             16, 17, 18, 19, 18, 17, 18, 17, 18, 19, 20, 21, 22, 23, 24, 23, 22,
             21, 20, 21, 22, 21, 22, 23, 22, 21, 22, 21, 22, 23, 22, 21, 20, 21,
             22, 23, 24, 25, 24, 23, 22, 21, 20, 21, 20, 21, 20, 19, 20, 19, 20,
             19, 18, 19, 18, 17, 16, 15, 14, 13, 14, 15, 14, 13, 12, 13, 14, 13,
             14, 13, 12, 11, 10, 11, 12, 11, 10,  9, 10,  9, 10,  9, 10, 11, 12,
             13, 12, 13, 14, 15, 14, 13, 14, 13, 14, 13, 14, 15, 14, 15, 16, 15,
             14, 13, 12, 13, 14, 15, 16, 15, 16, 15, 16, 15, 14, 15, 14, 15, 14,
             15, 16, 17, 16, 15, 16, 17, 18, 17, 18, 19, 20, 19, 20, 19, 20, 21,
             22, 21, 20, 19, 18, 17, 18, 17, 16, 15, 14, 15, 14, 15, 14, 13, 14,
             15, 14, 13, 14, 13, 14, 13, 12, 11, 12, 11, 10, 11, 10, 11, 10,  9,
             10, 11, 12, 11, 10,  9,  8,  9,  8,  7,  8,  9,  8,  9, 10,  9, 10,
             11, 10,  9, 10,  9, 10, 11, 12, 13, 12, 11, 10,  9, 10, 11, 10, 11,
             12, 13, 12, 11, 12, 11, 12, 13, 12, 11, 10, 11, 10, 11, 10,  9,  8,
              9, 10,  9,  8,  9,  8,  9,  8,  9, 10,  9, 10,  9,  8,  7,  6,  5,
              4,  5,  6,  5,  6,  7,  8,  7,  8,  9,  8,  7,  8,  7,  8,  9, 10,
              9,  8,  9, 10, 11, 12, 11, 10, 11, 10, 11, 12, 13, 12, 11, 12, 13,
             12, 11, 12, 13, 14, 15, 16, 17, 18, 17, 18, 19, 18, 17, 16, 17, 18,
             19, 20, 21, 20, 19, 20, 21, 22, 23, 22, 23, 24, 25, 24, 23, 24, 23,
             22, 21, 20, 21, 22, 21, 22, 23, 22, 21, 20, 19, 18, 19, 20, 21, 22,
             21, 22, 21, 22, 23, 24, 25, 24, 25, 24, 25, 24, 23, 24, 25, 26, 25,
             26, 27, 26, 27, 28, 29, 28, 27, 28, 29, 30, 29, 30, 29, 28, 29, 30,
             31, 32, 31, 30, 29, 30, 29, 30, 29, 30, 31, 32, 33, 34, 33, 34, 35,
             34, 33, 32, 31, 30, 31, 32, 33, 34, 35, 36, 35, 36, 37, 38, 39, 38,
             37, 38, 39, 38, 39, 38, 37, 36, 37, 38, 39, 40, 39, 38, 37, 36, 35,
             36, 35, 36, 35, 36, 37, 36, 37, 38, 37, 36, 37, 36, 37, 38, 39, 40,
             41, 42, 43, 44, 45, 46, 47, 48, 47, 48, 47, 48, 49, 50, 49, 48, 49,
             48, 47, 46, 45, 46, 45, 46, 45, 46, 45, 44, 43, 42, 43, 42, 43, 42,
             43, 44, 45, 44, 43, 42, 43, 42, 41, 40, 41, 42, 41, 42, 41, 42, 43,
             44, 43, 44, 45, 46, 47, 46, 47, 48, 49, 50, 51, 50, 51, 52, 51, 52,
             53, 52, 53, 52, 51, 52, 51, 52, 53, 54, 55, 56, 55, 54, 53, 54, 55,
             54, 55, 54, 55, 54, 55, 56, 57, 56, 57, 58, 57, 56, 57, 56, 57, 56,
             57, 58, 59, 58, 57, 58, 59, 58, 59, 58, 59, 58, 59, 58, 59, 60, 59,
             58, 57, 56, 57, 56, 55, 56, 55, 54, 53, 54, 55, 56, 57, 58, 57, 58,
             57, 56, 55, 54, 53, 54, 55, 56, 57, 56, 55, 56, 55, 56, 55, 54, 55,
             54, 53, 52, 51, 52, 53, 52, 51, 52, 51, 52, 51, 50, 49, 48, 49, 48,
             49, 48, 49, 50, 51, 52, 53, 54, 55, 54, 55, 56, 55, 54, 55, 54, 53,
             52, 51, 52, 51, 52, 53, 54, 55, 56, 55, 54, 55, 54, 53, 52, 53, 52,
             51, 52, 51, 52, 53, 54, 55, 54, 53, 52, 53, 52, 51, 50],
            dtype=int32)

in  : plt.plot(walk[:100])
out : [<matplotlib.lines.Line2D at 0x20d69fe1370>] # 산 같은 그림이 나온다.

계단에서 특정 위치에 도달하기까지의 시간 같은 좀 더 복잡한 통계를 구할 수 있는데,

계단의 처음 위치에서 최초로 10칸 떨어지기까지 얼마나 걸렸는지 확인해보자. np.abs(walk) >= 10으로 처음 위치에서

10칸 이상 떨어진 시점을 알려주는 불리언 배열을 얻을 수 있다.

우리는 최초의 10 혹은 -10인 시점을 구해야 하므로 불리언 배열에서 최댓값의 처음

index를 반환하는 argmax를 사용하자(True가 최댓값이다)

in  : (np.abs(walk) >= 10).argmax()
out : 297

여기서 argmax를 사용했지만 argmax는 배열 전체를 모두 확인하기 때문에 효과적인 방법은 아니다.
또한 이 예제에서는 True가 최댓값임을 이미 알고 있었다.

5000회로 다시 한번 해보자.

in  : nwalks = 5000
      nsteps = 1000
      draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 또는 1
      steps = np.where(draws > 0 ,1, -1)
      walks = steps.cumsum(1)
      walks
out : array([[  1,   2,   3, ...,  46,  47,  46],
             [  1,   0,   1, ...,  40,  41,  42],
             [  1,   2,   3, ..., -26, -27, -28],
             ...,
             [  1,   0,   1, ...,  64,  65,  66],
             [  1,   2,   1, ...,   2,   1,   0],
             [ -1,  -2,  -3, ...,  32,  33,  34]], dtype=int32)

in  : walks.max()
out : 122

in  : walks.min()
out : -128

이 데이터에서 누적합이 30혹은 -30이 되는 최소 시점을 계산해보자.

5000회의 시뮬레이션중 모든 경우가 30에 도달하지 않기 때문에 약간 까다로운데, any 메서드를 이용해서 해결 가능하다.

in  : hits30 = (np.abs(walks) >= 30).any(1)
      hits30
out : array([ True,  True,  True, ...,  True, False,  True])

in  : hits30.sum()
out : 3368

이 불리언 배열을 사용해서 walks에서 컬럼을 선택하고 절댓값이 30을 넘는 경우에 대해서 축 1의 argmax()값을 구하면

처음 위치에서 30칸 이상 멀어지는 최소 횟수를 구할 수 있다.

in  : crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
      crossing_times.mean()
out : 509.99762470308787

in  : crossing_times
out : array([133, 395, 343, ..., 409, 297, 747], dtype=int64)

다른 분포를 사용해서 여러 가지 시도를 해보자. normal 함수에 표준편차와 평균값을 넣어서
정규분포에서 표본을 추출하는 것처럼 그냥 다른 난수 발생 함수를 사용하기만 하면 된다.

in  : steps = np.random.normal(loc=0, scale=0.25, size=(nwalks,nsteps))
      steps
out : array([[ 0.34363475, -0.05522411,  0.05083405, ..., -0.33842559,
              -0.0230145 ,  0.23113296],
             [ 0.16099452, -0.50311406,  0.18528403, ...,  0.2945566 ,
              -0.12584062, -0.19622136],
             [ 0.26578933, -0.09885966,  0.23270679, ...,  0.16738906,
               0.060458  ,  0.28324365],
             ...,
             [ 0.24418693,  0.58143407,  0.01644158, ...,  0.11702547,
              -0.02086471,  0.00513563],
             [-0.15765375, -0.19420976,  0.0542288 , ...,  0.15976314,
               0.22189473,  0.07530368],
             [ 0.37258899, -0.0645595 , -0.42768902, ...,  0.28853102,
              -0.25261274, -0.32302591]])
728x90

'머신러닝 > numpy' 카테고리의 다른 글

NumPy의 기초(4)  (0) 2021.10.06
NumPy의 기초(3)  (0) 2021.09.09
NumPy의 기초(2)  (0) 2021.09.09
NumPy의 기초(1)  (0) 2021.09.09