티스토리 뷰

머신러닝

Numpy란?

김태훈 2021. 7. 20. 20:35

목차

    개요 - Numpy란?

    넘파이는 파이썬 패키지 중 하나로, 행렬과 선형대수를 다루는 패키지다. 머신러닝의 데이터들을 계산할 때 행렬과 선형대수를 이용한다. 파이썬에서 대부분의 머신러닝 패키지는 넘파이 기반으로 되어있기 때문에 넘파이와 머신러닝은 뗄레야 뗄 수 없는 관계라고 볼 수 있다.

    넘파이는 C를 기반으로 만들어졌기 때문에 빠른 속도를 자랑한다. 파이썬은 행렬을 저장할 때 리스트 구조로 저장하는데, 이 구조는 연산에 있어 좋지 않은 자료구조다. 따라서 파이썬의 리스트는 넘파이를 사용해 ndarray 라는 구조로 저장이 되고, 이 구조를 기반으로 여러 패키지가 만들어져 있다. 

    다만, 넘파이는 데이터를 조작하는 데 편리한 구조는 아니다. 따라서, 넘파이와 함께 Pandas가 데이터를 조작하기 위한 패키지로 사용된다. Pandas에 대해서는 다음 글에서 다루겠다.

     

    사전지식

    넘파이를 이해하기 전에 행렬에 대한 이해가 필요하다. 필자는 통계학과가 아니기 때문에 이 부분에 대한 설명은 생략하겠다. 행렬을 모른다면 기본적인 구조와 행렬의 사칙연산 정도는 이해를 하고 있어야 한다.

     

    문법

    넘파이 불러오기

     

    import numpy as np

    넘파이라는 모듈은 워낙에 자주 사용되는 모듈이다 보니 짧게 np로 표현하는 게 관례이다.

     

    ndarray 의 구조

    array([[ 0,  1,  2,  3,  4],
           [ 5,  6,  7,  8,  9],
           [10, 11, 12, 13, 14]])
           
    # 3 * 5 형태를 갖는 ndarray       
           
    array([[[ 0,  1],
            [ 2,  3],
            [ 4,  5],
            [ 6,  7],
            [ 8,  9]],
    
           [[10, 11],
            [12, 13],
            [14, 15],
            [16, 17],
            [18, 19]]])
    
    # 2 * 5 * 2 형태를 갖는 ndarray

     

    데이터의 타입 파악하기

    arr = np.array([1,2,3,4,5]) # numpy를 사용해서 list를 ndarray 로 만들어줬다. 바로 밑에서 설명
    
    type(arr) # numpy.ndarray -> arr의 타입을 보여준다. 
    arr.dtype # dtype('int64') -> ndarray 내부 값들이 어떤 데이터인지 보여준다.
    arr.shape # (5,) -> ndarray의 구조를 보여준다. 1차원의 데이터이며, 5개가 있는 걸 보여준다.

     

     

    행렬 만들기

    arr1 = np.array([1,2,3])
    # 파이썬의 리스트를 통해 ndarray를 만드는 메소드.
    
    np.zeros()
    np.ones()
    np.empty()
    # 하나의 값으로 이뤄진 ndarray를 만드는 메소드. 
    # 0으로 가득찬, 1로 가득찬, nan 값으로 가득찬 ndarray를 만들어준다.
    # 3개 다 동일한 구조이기 때문에 zeros 만 보이겠다.
    
    np.zeros(5) # array([ 0.,  0.,  0.,  0.,  0.])
    
    np.zeros((5,), dtype=int) # array([0, 0, 0, 0, 0])
    
    np.zeros((2, 1))
    # array([[ 0.],
    #		[ 0.]])
    
    s = (2,2)
    np.zeros(s)
    # array([[ 0.,  0.],
    #        [ 0.,  0.]])
    
    
    
    ary = np.arange(12) # array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
    # 0부터 함수 인자값 -1 까지 범위의 ndarray를 만들어준다.
    # np.reshape 와 함께 사용되는 경우가 많다
    
    ary.reshape(3,4)
    # array([[ 0,  1,  2,  3],
    #        [ 4,  5,  6,  7],
    #        [ 8,  9, 10, 11]])
    
    ary.reshape(12,1)
    # array([[ 0],
    #        [ 1],
    #        [ 2],
    #        [ 3],
    #        [ 4],
    #        [ 5],
    #        [ 6],
    #        [ 7],
    #        [ 8],
    #        [ 9],
    #        [10],
    #        [11]])
    
    
    ary.reshape(12,)
    # array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
    # shape -> (12,)
    
    ary.reshape(1,12)
    # array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11]])
    # shape -> (1,12)

    ary.reshape(12, ) 과 ary.reshape(1,12) 는 전혀 다른 결과를 갖는다. 

    ary.reshape(12, ) 의 차원은 1차원이고, ary.reshape(1,12) 는 2차원이다. 착각하지 말자!

     

     

     

    인덱싱

     

    단순 인덱싱

    arr_dim1 = np.array([3,5,7,2,10,1,4,6]) # array([ 3,  5,  7,  2, 10,  1,  4,  6])
    
    arr_dim1[3] # 2가 출력된다. 0번째 위치 -> 3, 1번째 위치 -> 5 ...
    arr_dim1[-1] # 6이 출력된다. -1 이라는 값을 사용하면 뒤에서부터 -1, -2, -3 순서로 인덱스가 붙는다.

    arr_dim1 이라는 1차원 ndarray 를 만들었다. 3이라는 값에 접근하고 싶으면 어떻게 해야 할까?

    파이썬과 마찬가지로 arr_dim1[인덱스] 라는 방법을 통해 접근할 수 있다.

    주의할 점은 인덱스는 1이 아니라 0부터 시작한다는 것이다. 즉, 3이라는 숫자는 4번째 위치에 있고, index는 3이기 때문에 arr_dim1[3]이라는 방법을 통해 접근이 가능하다.

    arr_dim2 = arr_dim1.reshape(2,4)
    # array([[ 3,  5,  7,  2],
    #        [10,  1,  4,  6]])

    10이라는 값에 접근하려면 어떻게 해야할까? 행렬에 대한 지식이 있다고 가정하고 말하겠다. 10의 위치는 2행 1열이다. 따라서 인덱스로 표현하면 1,0 위치에 있는 것이다. 따라서 arr_dim2[1][0] 을 사용해 10에 접근할 수 있다. 

    의문이 하나 생긴다. 2차원이야 행, 열 순서로 인덱싱이 진행된다고 하는데, 3차원부터는 그 순서가 어떻게 될까? 외우기만 해서는 4차원, 5차원, ... 차원이 많아질 경우는 어떤 규칙으로 진행이 될까? 그 부분에 대해 공부하려면 axis 라는 개념에 대해 알아야 한다. 밑에서 다루도록 하겠다.

     

    슬라이싱

    arr_dim1[4:7] # array([10,  1,  4])
    
    arr_dim2[0:2,:3]
    # array([[ 3,  5,  7],
    #        [10,  1,  4]])

    슬라이싱이란 건 x:y라는 범위를 지정해줘서 x 이상 y 미만의 인덱스 위치의 값들을 보여주는 방법이다. 

    n차원에서도 위와 같은 방법으로 사용이 가능하다.

    슬라이싱에서 빈칸으로 놔두는 경우에는 처음이나 끝이라는 의미가 된다.

     

    팬시 인덱싱

    arr_dim1[[4,7]] # array([10,  6])
    
    arr_dim2[0:2,[0,3]]
    # array([[ 3,  2],
    #        [10,  6]])

    팬시 인덱싱은 슬라이싱에서 할 수 없던 부분을 하게 해준다. 슬라이싱 같은 경우는 연속된 인덱스들의 경우만 보여줄 수 있었다. 하지만 팬시 인덱싱은 인덱스 집합을 지정해줘서 해당 인덱스 위치에 있는 값들만 선택할 수 있다. 

    위의 코드를 보면 1차원 ndarray에서 4번째 인덱스와 7번째 인덱스 값을 가져오는 걸 볼 수 있다.

    1차원 ndarray에서 팬시 인덱싱을 사용할 때 []를 안붙여주고 [4,7] 이런식으로 값을 넣으면, 1차원 ndarray 에서 2차원 값을 찾는 꼴이 돼 오류가 난다. 주의하자!

    2차원 ndarray 에서는 0이상 2 미만 행들을 선택했고, 0번째 열과 3번째 열을 선택했다. 여러 인덱싱 방법들을 복합적으로 사용할 수 있다.

     

    불린 인덱싱

     

    arr_dim1 > 5 # array([False, False,  True, False,  True, False, False,  True])
    
    arr_dim2 > 5
    # array([[False, False,  True, False],
    #        [ True, False, False,  True]])

    ndarray에 조건을 씌워주면 위에서 보는 것처럼 각 값들의 자리에 true, false가 들어간 ndarray 가 만들어지는 걸 볼 수 있다. 이 ndarray를 원래 ndarray 안에 넣어주면, False는 빼고 True 값만 가져와 새로운 1차원 ndarray 가 만들어진다!

    arr_dim1[arr_dim1 > 5] # array([ 7, 10,  6])
    arr_dim2[arr_dim2 > 5] # array([ 7, 10,  6])

     

    정렬

    정렬에는 두 가지 방식이 있다. 원래 행렬을 건들지 않고 새로운 행렬을 만드는 방법과 원래 행렬의 값을 변경하는 방법이다.

    원래 행렬을 건들지 않는 방식은 np.sort() 이고, 바꾸는 방법은 ndarray이름.sort() 이다.

    ndarray이름.sort() 라는 방식은 아무것도 리턴하지 않는다. 조심하자.

    원래 행렬을 정리하려고 ndarray이름 = ndarray이름.sort() 를 하면 ndarray 값들은 전부 날아간다.

    arr = np.array([3,5,7,2,10,1,4,6])
    
    new_arr = np.sort(arr)
    
    print(arr) # array([ 3,  5,  7,  2, 10,  1,  4,  6])
    
    print(new_arr) # array([ 1,  2,  3,  4,  5,  6,  7, 10])
    arr = np.array([3,5,7,2,10,1,4,6])
    
    new_arr = arr.sort()
    
    arr # array([ 1,  2,  3,  4,  5,  6,  7, 10])
    
    type(new_arr) # NoneType

    axis 이해하기

     

    먼저 자, 다음 문제들을 풀어보자. 

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

    답은 4다. 1차원에서는 뭐 너무 쉽다. 다음 문제를 풀어보자.

    arr_2dim = np.arange(16).reshape(4,4)
    # array([[ 0,  1,  2,  3],
    #        [ 4,  5,  6,  7],
    #        [ 8,  9, 10, 11],
    #        [12, 13, 14, 15]])
    
    arr_2dim[3][2]

    답은 14다. 2차원까지도 행렬 개념을 아니까 쉬울 것이다. 행이 먼저, 열이 그다음. 가장 앞에 배치되는 행의 axis = 0, 그 다음으로 나오는 열은 axis=1 이다. 이제 3차원을 한번 풀어보자.

    arr_3dim = np.arange(32).reshape(4,2,4)
    
    # 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]],
    
    #        [[24, 25, 26, 27],
    #         [28, 29, 30, 31]]])
    
    arr_3dim[3][0][2]

    그림도 첨부하겠다.

    새삼스럽지만 나 글씨 정말 못쓰는구나... 알아만 봤으면 좋겠다. 사실 그림을 봐도 모를 것이다. 왜냐하면 행, 열, 높이 중 인덱스 순서가 어떻게 되는지를 모르겠으니까.

    3차원에서는 높이, 행, 열 순이다. axis = 0은 높이, 1이 행, 2가 열이라는 뜻이다. 즉, arr_3dim[3][0][2] 는 높이 인덱스 3, 행 인덱스 0, 열 인덱스 2. 즉, 26이다.

    3차원까지는 axis를 외워서 할 수 있다 치자. 그렇다면 4차원부터는 어떻게 해야할까? 4차원부터는 그림을 그릴 수도 없고 우리가 인식할 수 있는 범위 밖이다. 

    차원이 변해도 axis를 찾을 수 있어야 한다. 쉽게 찾는 방법을 가르쳐주겠다. 자, 주석으로 첨부한  3차원 ndarray를 살펴보자. 

    # 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]],
    # 
    #        [[24, 25, 26, 27],
    #         [28, 29, 30, 31]]])

    저 ndarray 를 가장 큰 묶음으로 자르면 몇개가 되는가? [[]] 로 묶여있는 4개의 묶음으로 나눌 수 있다. 

    1번 묶음 0~7, 2번 묶음 8~15 이런식으로 묶인다. 가장 큰 묶음 단위가 axis = 0이다.

    그 다음으로 큰 묶음은 [0,1,2,3] [4,5,6,7] 이 있다. 이 단위가 axis = 1이다. 이런 식으로 생각하면 차원이 커져도 axis를 잡을 수 있다.

     

    이 axis라는 개념을 잘 잡고 있어야 행렬의 덧셈, concentrate 등을 잘 사용할 수 있다. 

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

    1. 머신러닝이란?  (0) 2021.07.18
    머신러닝 목차  (0) 2021.07.18
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크
    TAG
    more
    «   2025/05   »
    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 27 28 29 30 31
    글 보관함