ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Python Programming (6) - 선형대수
    Python Programming 2020. 3. 28. 06:53
    728x90
    6.Linear_algebra

    6. Linear algebra

    Python에서의 선형 대수

    • 이 단원에서는 선형 대수의 여러 개념을 Python 기본 함수와 리스트 등을 이용하여 구현하는 연습을 한다.
    • 벡터를 생성하고, 벡터와 행렬 연산을 수행할 수 있는 함수를 작성한다.
    • 이 후, Python 모듈인 numpy를 이용하여 직접 만들었던 선형 대수의 기능과 비교해 본다.

    벡터 - Vectors

    • 벡터는 벡터 공간의 원소를 벡터라 하며,
    • 백터들은 서로 더하거나 스칼라에 의해 곱해질 수 있다.
    • 벡터를 숫자들의 리스트라고 생각해 보자.
    In [1]:
    height_weigth_age = [70,    # inches
                        170,    # pounds
                        40 ]    # years
    
    In [2]:
    grades = [95,    # exam1
              80,    # exam2
              75,    # exam3
              62 ]   # exam4
    
    • Python의 list는 벡터 연산을 제공하지 않기 때문에, 벡터 연산을 추가해 보자.

    벡터 합과 차

    • 원소별로 계산 : 다음을 구현하고 싶음
      • [1, 2] 더하기 [3, 4] = [4, 6]
      • [5, 3] 빼기 [1, 7] = [4, -4]
    In [3]:
    def vector_add(v, w):
        """adds two vectors componentwise"""
        return [v_i + w_i for v_i, w_i in zip(v,w)]
    
    In [4]:
    vector_add([1,2], [3,4])
    
    Out[4]:
    [4, 6]
    In [5]:
    vector_add([1,2,3], [3,4,5])
    
    Out[5]:
    [4, 6, 8]
    In [6]:
    v = [1, 2, 3]
    w = [2, 3, 4]
    z = vector_add(v, w)
    y = vector_add(z, v)
    print(y)
    
    [4, 7, 10]
    
    In [7]:
    def vector_subtract(v, w):
        """subtracts two vectors componentwise"""
        return [v_i - w_i for v_i, w_i in zip(v,w)]
    
    In [8]:
    vector_subtract([0, 0, 1], [1, 2, 3])
    
    Out[8]:
    [-1, -2, -2]

    여러 개의 벡터들의 합

    • 벡터들의 리스트가 있을 때, 리스트 내의 벡터들을 원소 별로 합하기
    In [9]:
    def vector_sum(vectors):
        result = vectors[0]
        for vector in vectors[1:]:
            result = vector_add(result, vector)
        return result
    
    In [10]:
    vectors = [[1, 2, 3],
               [2, 3, 0],
               [0, 1, -2]]
    
    vector_sum(vectors)
    
    Out[10]:
    [3, 6, 1]
    In [11]:
    from functools import reduce
    # 앞의 vector_sum과 같은 기능을 가진다.
    def vector_sum(vectors):
        return reduce(vector_add, vectors)
    
    In [12]:
    vectors = [[1, 2, 3],
               [2, 3, 0],
               [0, 1, -2]]
    
    vector_sum(vectors)
    
    Out[12]:
    [3, 6, 1]
    In [13]:
    # 내부적으로는 다음과 동치
    vector_add(vector_add([1, 2, 3], [2, 3, 0]), [0, 1, -2])
    
    Out[13]:
    [3, 6, 1]

    스칼라 곱

    • 목표 : 3*[1, 2, 3] = [3, 6, 9]
    In [14]:
    def scalar_multiply(c, v):
        return [c * vi for vi in v]
    
    In [15]:
    scalar_multiply(3, [1, 2, 3])
    
    Out[15]:
    [3, 6, 9]
    • 컴포넌트별 평균 : vector_mean([1, 2], [2, 4], [3, 6]) == [3, 6]
    In [16]:
    def vector_mean(vectors):
        n = len(vectors)
        return scalar_multiply(1/n, vector_sum(vectors))
    
    In [17]:
    vector_mean([[1, 2], [2, 4], [3, 6]])
    
    Out[17]:
    [2.0, 4.0]
    • 단, Python 2.x의 경우 위의 나누기에 실수 나누기를 적용하기 위해서는 파일 위쪽에 다음을 표기
      • Python 3.x는 실수 나누기가 적용되기 때문에 상관 없음
    In [18]:
    from __future__ import division
    

    dot product

    • 목표 :
      • dot( [1, 2, 3], [0, 1, 2]) = sum([1 * 0, 2 * 1, 3 * 2]) = sum([0, 2, 6]) = 8
    In [19]:
    def dot(v, w):
        """v_1 * w_1 + ... + v_n * w_n"""
        return sum(v_i * w_i for v_i, w_i in zip(v, w))
    
    In [20]:
    dot([1, 2, 3], [0, 1, 2])
    
    Out[20]:
    8

    제곱합

    • 벡터 원소들의 제곱의 합
    In [21]:
    def sum_of_squares(v):
        """v_1 * v_1 + ... + v_n * v_n"""
        return dot(v, v)
    
    In [22]:
    sum_of_squares([0, 1, 2])
    
    Out[22]:
    5
    • 벡터의 크기(magnitude) : 제곱합의 제곱근
    In [23]:
    import math
    def magnitude(v):
        return math.sqrt(sum_of_squares(v))
    
    In [24]:
    magnitude([3, 4])
    
    Out[24]:
    5.0
    In [25]:
    magnitude([5, 12])
    
    Out[25]:
    13.0

    벡터 사이의 거리

    • 한 벡터에서 다른 벡터를 뺀 후, 크기를 구하는 것과 동일
    In [26]:
    def distance(v, w):
        return magnitude(vector_subtract(v, w))
    
    In [27]:
    distance([1, 2], [2, 3])
    
    Out[27]:
    1.4142135623730951

    NumPy

    • NumPy는 Python에서의 수학/과학 컴퓨팅의 기본 패키지로서
    • numpy 모듈에는 지금까지 행한 벡터 연산들이 구현되어 있음.
    • numpy 패키지 (모듈)는 Python을 사용하는 거의 모든 수치 계산에 사용된다.
    • Python을 위한 벡터, 행렬 및 고차원 데이터 구조를 제공한다.
    • http://www.numpy.org/

    numpy 모듈을 이용하기 위해서는 다음과 같이 import를 먼저 진행한다. 시작 시 한 번만 불러오면 된다.

    In [28]:
    import numpy as np  
    

    다음은 dot product 예제이다.

    In [29]:
    # dot product
    np.dot([1,2], [3,4])
    
    Out[29]:
    11

    Numpy array

    • numpy의 다양한 기능은 array라는 데이터구조를 바탕으로 이루어진다.

      • 효율적이고 계산이 편리함
    • numpy array는 다양한 방법을 통해 만들 수 있다.

      • 파이썬 리스트 또는 튜플을 이용하는 방법
      • arange, linspace 등과 같이 numpy 배열을 생성하는 데 사용되는 함수를 사용하는 방법
      • 파일에서 데이터를 읽어들이는 방법
    • 다차원 배열을 구현
    • 수학적 계산에 특화

    Python list로부터 numpy array 만들기

    In [30]:
    import numpy as np  # 이미 한 번 import하였으면 다시 하지 않아도 된다.
    a = np.array([0, 1, 2, 3])
    
    a
    
    Out[30]:
    array([0, 1, 2, 3])
    In [31]:
    print(a)
    
    [0 1 2 3]
    
    In [32]:
    a.ndim  # 1 차원
    
    Out[32]:
    1
    In [33]:
    a.shape  # 형태 : (4,)
    
    Out[33]:
    (4,)
    In [34]:
    len(a)  # 4
    
    Out[34]:
    4
    In [35]:
    # matrix: Python list로 이루어진 list를 이용하여 matrix 만들기
    b = np.array([[0,1,2], [3,4,5]])
    
    b
    
    Out[35]:
    array([[0, 1, 2],
           [3, 4, 5]])
    In [36]:
    b.ndim  # 2차원
    
    Out[36]:
    2
    In [37]:
    b.shape # 형태 (2,3)
    
    Out[37]:
    (2, 3)
    In [38]:
    len(b)  # 2 : 첫번째 차원의 길이
    
    Out[38]:
    2

    array는 list와 비슷해 보이지만, numpy에서 array라는 별도의 데이터구조를 이용하는 몇 가지 이유가 있다.

    • Python list는 동적으로 할당되며, list내의 원소들이 서로 다른 데이터형을 가질 수 있다.
    • 이러한 점은 벡터나 행렬 계산을 느리게 혹은 불가능하게 한다.
    • 반면 numpy array내의 원소들의 데이터 형은 일정하고(homogeneous), 변하지 않기 때문에, 메모리 효율적이고, 행렬이나 벡터 계산을 빠르게 할 수 있다.
    • 이미 데이터 타입이 결정된 array의 원소를 다른 데이터 타입으로 변경하면 에러가 발생한다.
    In [39]:
    b.dtype
    
    Out[39]:
    dtype('int32')
    In [40]:
    b[0,0] = "a"
    
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-40-9c6a29519c7a> in <module>()
    ----> 1 b[0,0] = "a"
    
    ValueError: invalid literal for int() with base 10: 'a'

    array 생성

    numpy에서는 다양한 방법을 통해 array를 생성할 수 있도록 도와준다.

    In [43]:
    a = np.arange(10)  # range 함수와 비슷
    
    a
    
    Out[43]:
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    In [44]:
    b = np.arange(1, 9, 2) 
    
    b
    
    Out[44]:
    array([1, 3, 5, 7])
    In [45]:
    c = np.linspace(0, 1, 6)  #시작, 끝, 숫자 개수
    
    c
    
    Out[45]:
    array([0. , 0.2, 0.4, 0.6, 0.8, 1. ])
    In [46]:
    d = np.ones((3, 3))  # 1로 이루어진 다차원 배열
    
    d
    
    Out[46]:
    array([[1., 1., 1.],
           [1., 1., 1.],
           [1., 1., 1.]])
    In [47]:
    e = np.zeros((2, 2))  # 0으로 이루어진 다차원 배열
    
    e
    
    Out[47]:
    array([[0., 0.],
           [0., 0.]])
    In [48]:
    f = np.eye(3)  # identity 행렬
    
    f
    
    Out[48]:
    array([[1., 0., 0.],
           [0., 1., 0.],
           [0., 0., 1.]])
    In [49]:
    g = np.diag(np.array([1, 2, 3, 4]))  # 대각 행렬
    
    g
    
    Out[49]:
    array([[1, 0, 0, 0],
           [0, 2, 0, 0],
           [0, 0, 3, 0],
           [0, 0, 0, 4]])

    array 생성(2)

    numpy.random 모듈을 이용한 랜덤 array 생성

    In [50]:
    np.random.rand(4)       # uniform in [0, 1]
    
    Out[50]:
    array([0.76767629, 0.33294295, 0.33645484, 0.9047073 ])
    In [51]:
    np.random.randn(4)      # standard normal
    
    Out[51]:
    array([-0.24796071,  0.56965874,  1.65606846,  0.51729609])
    In [52]:
    2.5 * np.random.randn(4) + 3   # 평균 3, 표준편차 2.5인 정규분포
    
    Out[52]:
    array([3.81524923, 2.76766133, 3.14255768, 0.1636803 ])

    위 예제들에서 경우에 따라 마침표 (예 : 2. vs 2)가 표시된다는 것을 알 수 있다. 이는 데이터 유형의 차이 때문이다. 마침표가 없는 것은 정수형 데이터, 마침표가 있는 것은 실수형 데이터를 의미한다. numpy array는 한 종류의 데이터 유형만 가질 수 있다.

    Basic Slicing and Indexing

    • Python list와 비슷하게 [ ]를 이용하여 원소에 접근한다.
    • slicing 또한 Python list와 마찬가지로 start:stop:step를 이용한다.
    In [53]:
    v = np.array([1, 2, 3, 4, 5, 6])
    v
    
    Out[53]:
    array([1, 2, 3, 4, 5, 6])
    In [54]:
    v[0]
    
    Out[54]:
    1
    In [55]:
    v[2], v[-1]  # -1은 마지막 원소
    
    Out[55]:
    (3, 6)
    In [56]:
    v[1:5:2]
    
    Out[56]:
    array([2, 4])
    In [57]:
    v[::-1]  # 순서 뒤집기
    
    Out[57]:
    array([6, 5, 4, 3, 2, 1])

    2차원 array에서는 행(row)과 열(column)의 인덱스에 기반한다.

    In [58]:
    M = np.array([[1, 2], [3, 4]])
    M
    
    Out[58]:
    array([[1, 2],
           [3, 4]])
    In [59]:
    M[1, 1]
    
    Out[59]:
    4
    In [60]:
    # 인덱스 하나만 사용하면 하나의 행을 슬라이싱
    M[1]  # row 1 
    
    Out[60]:
    array([3, 4])
    In [61]:
    M[1, :] # row 1 (여기서 :는 모든 열을 의미)
    
    Out[61]:
    array([3, 4])
    In [62]:
    M[:, 1] # column 1 (여기서 :는 모든 행을 의미)
    
    Out[62]:
    array([2, 4])
    In [63]:
    M[0, 0] = 10
    
    M
    
    Out[63]:
    array([[10,  2],
           [ 3,  4]])

    대입 연산도 마찬가지로 이루어질 수 있다.

    In [64]:
    M[1, :] = -1
    
    M
    
    Out[64]:
    array([[10,  2],
           [-1, -1]])
    In [65]:
    M[:, 1] = 777
    
    M
    
    Out[65]:
    array([[ 10, 777],
           [ -1, 777]])
    In [66]:
    a = np.array([[n + m * 10 for n in range(6)] for m in range(6)])
    
    a
    
    Out[66]:
    array([[ 0,  1,  2,  3,  4,  5],
           [10, 11, 12, 13, 14, 15],
           [20, 21, 22, 23, 24, 25],
           [30, 31, 32, 33, 34, 35],
           [40, 41, 42, 43, 44, 45],
           [50, 51, 52, 53, 54, 55]])

    <img src = "figure/numpy_indexing.png", width = 500, height = 500> https://scipy-lectures.org/intro/numpy/array_object.html#what-are-numpy-and-numpy-arrays

    In [67]:
    a[0, 3:5]
    
    Out[67]:
    array([3, 4])
    In [68]:
    a[4:, 4:]
    
    Out[68]:
    array([[44, 45],
           [54, 55]])
    In [69]:
    a[:, 2]
    
    Out[69]:
    array([ 2, 12, 22, 32, 42, 52])
    In [70]:
    a[2::2, ::2]
    
    Out[70]:
    array([[20, 22, 24],
           [40, 42, 44]])
    In [71]:
    a[1:3, 1:3]
    
    Out[71]:
    array([[11, 12],
           [21, 22]])
    In [72]:
    a[::2, ::2]
    
    Out[72]:
    array([[ 0,  2,  4],
           [20, 22, 24],
           [40, 42, 44]])

    <img src = "figure/numpy_fancy_indexing.png", width = 600, height = 600>

    Advanced Indexing

    :를 이용하는 slicing이 아닌 정수 혹은 Boolean의 list, array, tuple 등으로 이루어진 indexing을 말한다.

    In [73]:
    # 행과 열의 index들이 모두 slicing이 아닐 때 - 정수로 이루어진 리스트
    a[[0,1,2,3,4], [1,2,3,4,5]]
    
    Out[73]:
    array([ 1, 12, 23, 34, 45])
    In [74]:
    a[(0,1,2,3,4), (1,2,3,4,5)]
    
    Out[74]:
    array([ 1, 12, 23, 34, 45])
    In [75]:
    # 아래와 같이 해석할 수 있다.
    np.array([a[0,1], a[1,2], a[2,3], a[3,4], a[4,5]])
    
    Out[75]:
    array([ 1, 12, 23, 34, 45])
    In [76]:
    # slicing과는 다르다.
    a[0:5, 1:6]
    
    Out[76]:
    array([[ 1,  2,  3,  4,  5],
           [11, 12, 13, 14, 15],
           [21, 22, 23, 24, 25],
           [31, 32, 33, 34, 35],
           [41, 42, 43, 44, 45]])
    In [77]:
    # 행과 열의 index중 하나라도 slicing일 때는 slicing의 규칙을 따른다.
    a[3:, [0,2,5]]
    
    Out[77]:
    array([[30, 32, 35],
           [40, 42, 45],
           [50, 52, 55]])
    In [78]:
    # Boolean array를 이용할 수도 있다.
    mask = np.array([1, 0, 1, 0, 0, 1], dtype=bool)
    mask
    
    Out[78]:
    array([ True, False,  True, False, False,  True])
    In [79]:
    a[mask, 2]
    
    Out[79]:
    array([ 2, 22, 52])
    In [80]:
    row_indices = [1, 2, 3]
    a[row_indices]
    
    Out[80]:
    array([[10, 11, 12, 13, 14, 15],
           [20, 21, 22, 23, 24, 25],
           [30, 31, 32, 33, 34, 35]])
    In [81]:
    col_indices = [1, 2, -1]             # -1은 마지막 원소를 나타냄
    a[row_indices, col_indices]
    
    Out[81]:
    array([11, 22, 35])
    In [82]:
    b = np.array([n for n in range(5)])
    b
    
    Out[82]:
    array([0, 1, 2, 3, 4])
    In [83]:
    row_mask = np.array([True, False, True, False, False])
    b[row_mask]
    
    Out[83]:
    array([0, 2])
    In [84]:
    # same thing
    row_mask = np.array([1,0,1,0,0], dtype=bool)
    b[row_mask]
    
    Out[84]:
    array([0, 2])
    In [85]:
    x = np.arange(0, 10, 0.5)
    x
    
    Out[85]:
    array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. ,
           6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])
    In [86]:
    x[(5 < x) * (x < 7.5)]
    
    Out[86]:
    array([5.5, 6. , 6.5, 7. ])
    • 행렬의 원소별 곱셈
    In [87]:
    M * N
    
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-87-8da4014d6915> in <module>()
    ----> 1 M * N
    
    NameError: name 'N' is not defined

    Numpy array에서의 복사와 참조

    • 성능 향상을 위해 파이썬에서의 많은 경우 복사를 하지 않고 참조를 하는 경우가 많다.
    In [88]:
    A = np.array([[1, 2], [3, 4]])
    
    A
    
    Out[88]:
    array([[1, 2],
           [3, 4]])
    In [89]:
    # B는 A를 참조만 한다.
    B = A
    
    In [90]:
    # B의 변화는 A에 영향을 미침
    B[0,0] = 10
    
    B
    
    Out[90]:
    array([[10,  2],
           [ 3,  4]])
    • 만약 이런 현상을 원치 않는다면, .copy()를 이용하여 복사한다.
    In [91]:
    C = np.copy(A)
    C[0, 0] = -1
    
    C
    
    Out[91]:
    array([[-1,  2],
           [ 3,  4]])

    array slicing 또한 기본적으로 참조를 바탕으로 하므로 주의해야 한다.

    • view를 제공한다고도 표현한다.
    In [92]:
    # A는 바뀌지 않음
    A
    
    Out[92]:
    array([[10,  2],
           [ 3,  4]])
    In [93]:
    a = np.arange(10)
    b = a[::2]
    b
    
    Out[93]:
    array([0, 2, 4, 6, 8])
    In [94]:
    b[0] = 12
    b
    
    Out[94]:
    array([12,  2,  4,  6,  8])
    In [95]:
    a   # 주의
    
    Out[95]:
    array([12,  1,  2,  3,  4,  5,  6,  7,  8,  9])
    In [96]:
    a = np.arange(10)
    c = a[::2].copy()  #복사하여 사용하면
    c[0] = 12
    c
    
    Out[96]:
    array([12,  2,  4,  6,  8])
    In [97]:
    a
    
    Out[97]:
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    In [98]:
    # Numpy array의 경우
    a = np.arange(10)
    b = a[:]   # 참조가 발생함
    b[0] = 10
    a
    
    Out[98]:
    array([10,  1,  2,  3,  4,  5,  6,  7,  8,  9])
    In [99]:
    # Python list의 경우는 다르다.
    x = list(range(10))
    y = x[:]     # list는 이 경우 복사함
    y[0] = 10
    x
    
    Out[99]:
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    반면 정수 리스트나 Boolean 리스트를 바탕으로 하는 advanced indexing은 복사를 기본으로 한다.

    In [100]:
    a = np.arange(10)
    d = a[[0,1,2]]
    
    In [101]:
    d[0] = 10
    d
    
    Out[101]:
    array([10,  1,  2])
    In [102]:
    # a는 바뀌지 않음
    a
    
    Out[102]:
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

    numpy와 numpy.linalg를 이용한 선형대수

    • numpy는 벡터와 행렬 연산에 있어 강력한 기능을 가지고 있다.
    • 벡터 합과 차
    In [103]:
    a = np.array([3,1,-1])
    b = np.arange(3)
    c = a + b
    d = b - a
    
    • 스칼라 곱
    In [104]:
    4 * a
    
    Out[104]:
    array([12,  4, -4])
    • 제곱합
    In [105]:
    np.sum(a**2)
    
    Out[105]:
    11
    • dot product
    In [106]:
    np.dot(a, b)
    
    Out[106]:
    -1
    • 벡터 사이의 거리
    In [107]:
    np.linalg.norm(a-b)
    
    Out[107]:
    4.242640687119285
    • 벡터 원소별 곱셈
    In [108]:
    a * b
    
    Out[108]:
    array([ 0,  1, -2])
    In [109]:
    M = np.array([[1,2], [3,4]])
    
    M
    
    Out[109]:
    array([[1, 2],
           [3, 4]])
    • transpose
    In [110]:
    M.T
    
    Out[110]:
    array([[1, 3],
           [2, 4]])
    • 행렬식
    In [111]:
    np.linalg.det(M)
    
    Out[111]:
    -2.0000000000000004
    • 역행렬
    In [112]:
    np.linalg.inv(M)
    
    Out[112]:
    array([[-2. ,  1. ],
           [ 1.5, -0.5]])
    • 해 찾기
    In [113]:
    c = np.array([2,1])
    np.linalg.solve(M, c)
    
    Out[113]:
    array([-3. ,  2.5])
    • 행렬곱
    In [114]:
    N = np.array([[-1,1],[2,1]])
    
    N
    
    Out[114]:
    array([[-1,  1],
           [ 2,  1]])
    In [115]:
    np.matmul(M,N)
    
    Out[115]:
    array([[3, 3],
           [5, 7]])
    • 행렬의 원소별 곱셈
    In [116]:
    M * N
    
    Out[116]:
    array([[-1,  2],
           [ 6,  4]])
    728x90

    댓글

Designed by Tistory.