실습_1. 선형대수학(Numpy)

Updated:

NumPy 행렬

NumPy는 파이썬에서 행렬 계산을 쉽게 할 수 있도록 도와줍니다. NumPy의 다양한 함수를 이용하면 기계학습에서 사용되는 수식을 쉽게 구현할 수 있습니다.

NumPy를 사용하기 위해서는 먼저 import를 통해 라이브러리를 불러와야 합니다.

import numpy as np
  • np.array()

array()를 이용하면 n차원 행렬을 쉽게 생성할 수 있습니다. 우리가 접하는 데이터는 보통 1~3차원 행렬에 속하지만 복잡한 데이터를 다룰 때에는 고차원 행렬이 필요합니다. 2 \times 22×2 차원 행렬을 생성하는 방법은 다음과 같습니다.

A = np.array([[1, 2],
              [3, 4]])

대괄호의 짝과 수, 쉼표의 위치에 주의하세요. 고차원 행렬을 작성할 때 각 행을 다른 줄에 작성하면 코드의 가독성을 높일 수 있습니다.

  • 실습

matrix_tutorial() 함수 안에 배열 3 \times 43×4 의 크기를 가진 행렬 A를 선언하세요.

import numpy as np

def main():
    print(matrix_tutorial())

def matrix_tutorial():
    # Create the matrix A here...
    A=np.array([[1,4,5,8],[2,1,7,3],[5,4,5,9]])

    return A

if __name__ == "__main__":
    main()

NumPy 산술연산자

  • np.sum()
    sum()을 이용하면 행렬 안의 모든 값의 합을 빠르게 구할 수 있습니다.
>>> A = np.array([[1, 2], [3, 4]])
>>> print(np.sum(A))
10

+, -, /, *
NumPy 행렬에는 사칙연산을 적용할 수 있습니다. 수학에서는 이와 같은 연산을 허용하지 않지만 큰 데이터를 다룰 때 편의를 위해 만들어진 기능입니다. A 행렬에 사칙연산을 A의 모든 원소에 해당 연산이 적용됩니다.

>>> A = np.array([[1, 2], [3, 4]])
>>> print(A + 2)
[[3 4]
 [5 6]]
>>> print(A / 5)
[[0.2 0.4]
 [0.6 0.8]]
  • np.mean, np.median, np.std, np.var
    mean(), median(), std(), var()를 이용하면 행렬의 평균값, 중간값, 표준 편차값, 분산값을 코드 한 줄로 구할 수 있습니다. 이 외에도 NumPy는 통계 처리를 위한 다양한 함수를 제공합니다: Numpy - Statistics.
>>> A = np.array([1, 2, 4, 5, 5, 7, 10, 13, 18, 21])
>>> print(np.mean(A))
8.6
>>> print(np.median(A))
6.0
>>> print(np.std(A))
6.437390775772433
>>> print(np.var(A))
41.44
  • 실습

A 원소의 합이 1이 되도록 표준화(Normalization)를 적용하고 결과값을 A에 다시 저장하세요. ([42, 58][42,58] 에 표준화를 적용하면 [0.42, 0.58][0.42,0.58] 이 됩니다.

matrix_tutorial() 함수가 A의 분산(Variance)값을 리턴하도록 리턴값을 변경하세요.

import numpy as np

def main():
    print(matrix_tutorial())

def matrix_tutorial():
    A = np.array([[1,4,5,8], [2,1,7,3], [5,4,5,9]])

    # 아래 코드를 작성하세요.
    Sum = np.sum(A)
    
    A = A/Sum
    
    A = np.var(A)
    
    return A

if __name__ == "__main__":
    main()

행렬에 논리연산자 적용하기

앞으로 보다 쉽게 행렬을 입력할 수 있도록 다음과 같은 방법으로 배열을 입력받겠습니다.

먼저 행렬의 크기를 입력받습니다. 행의 수와 열의 수를 의미하는 두 정수를 받습니다. 두 개의 숫자는 공백으로 나누어집니다. 그 다음 한 줄에 열의 수 만큼의 숫자가 입력됩니다. [행의 수] 만큼의 줄이 입력되고 총 [행의 수] \times× [열의 수] 만큼의 숫자를 입력받게 됩니다.

    3 5
    1 2 6 3 8
    11 0 -1 3 1
    9 0 7 -3 4
  • np.transpose()

transpose() 또는 T를 이용하면 전치행렬을 구할 수 있습니다. 배열의 (i, j)(i,j) 번째 원소를 (j, i)(j,i)번째 원소로 변환합니다.

>>> A = np.array([[1, 2, 3], [4, 5, 6]])
>>> print(A.T)
[[1 4]
 [2 5]
 [3 6]]
  • np.linalg.inv

inv()는 행렬의 역행렬(inverse)를 구할 때 사용됩니다. NumPy의 선형대수학 관련 세부 패키지 linalg를 사용하기 때문에, 조금 더 긴 명령어를 사용합니다.

>>> A = np.array([[1, 2], [3, 4]])
>>> print(np.linalg.inv(A))
  • np.dot

dot()은 두 행렬의 곱셈, 혹은 두 벡터의 내적(dot product)을 구할 때 사용됩니다. 이때 두 행렬의 크기 또는 shape이 맞지 않으면 오류가 발생합니다.

>>> A = np.array([[1, 2, 3], [1, 2, 1]])
>>> B = np.array([[2, 1, 3], [-1, 0, 5]])
>>> C = np.dot(A, B)
'ValueError: shapes (2,3) and (2,3) not aligned'
>>> B = B.transpose()
>>> C = np.dot(A, B)
>>> print(C)
[[13 14]
 [ 7  4]]
  • 실습

지금까지 배운 행렬 사용 방법을 모두 응용해보겠습니다.

앞으로 자주 사용될 get_matrix() 함수를 살펴보고 동작 방법을 숙지하세요.

A의 전치행렬(transpose) B를 생성하세요.

B의 역행렬을 구하여 C에 저장하세요. 역행렬을 구하는 것이 불가능하면 문자열 “not invertible”을 리턴합니다.

matrix_tutorial() 함수의 리턴값으로 0보다 큰 C의 원소를 모두 세어 개수를 리턴하세요. sum()의 인자로 해당 조건 C > 0을 입력하면 0보다 큰 원소를 쉽게 셀 수 있습니다.

import numpy as np

def main():
    A = get_matrix()
    print(matrix_tutorial(A))

def get_matrix():
    mat = []
    [n, m] = [int(x) for x in input().strip().split(" ")]
    for i in range(n):
        row = [int(x) for x in input().strip().split(" ")]
        mat.append(row)
    return np.array(mat)

def matrix_tutorial(A):
    
    # 아래 코드를 완성하세요.
    B = np.transpose(A)
    
    print(B)
    try:
        C = np.linalg.inv(B)
    except:
        return 'not invertible'
    return np.sum(C > 0)

if __name__ == "__main__":
    main()

벡터 연산과 Numpy로 그림 그리기

이번 프로젝트에서는 Numpy를 이용한 벡터 연산으로 그림을 그려보겠습니다.

캔버스
그림을 그리기 위해서는 그림을 그릴 공간이 필요합니다. 이 프로젝트에서는 이 공간을 xrange, yrange 라는 변수로 지정하겠습니다. 만약,

xrange = [1, 3]
yrange = [2, 4]

라면, 그림을 그릴 캔버스는 (1, 2)(1,2), (3, 4)(3,4) 로 지정된 공간을 사용하게 됩니다.

그림 그리기
그림을 그리기 위해서, 다음 방식을 사용하겠습니다. 어떤 함수 f와 매우 작은 숫자 threshold에 대해,

캔버스 내에 점 P = (x, y)을 임의로 생성한다.
f(P) < threshold 라면 점을 찍는다. 만약 그렇지 않다면, 점을 찍지 않는다.
이것을 100,000 회 반복한다.
왜 f(P) == 0 일때 점을 찍지 않고, 아주 작은 값 threshold 보다 작을 때 점을 찍는지, 한번 생각해 보세요!

예제: 원 그리기
이 예제는 circle 함수에 정의되어 있습니다.

(0, 0)(0,0) 이 중심이고 반지름 1인 원을 그리는 방정식은 다음과 같습니다.

x^2 + y^2 = 1

위의 그림을 그리는 방식을 생각하면, 정확히 원 위에 있는 점들에 대해서 circle(P) 은 0을 가져야 합니다. 그러므로, circle(P) 는 다음과 같이 정의할 수 있습니다.

x = P[0]
y = P[1]
return sqrt(x ** 2 + y ** 2) - 1
return sqrt(np.sum(P * P)) - 1
  • 프로젝트

예제로 주어진 원 circle 과 다이아몬드 diamond 함수를 이용해 원과 다이아몬드 그림을 그려봅니다. 그리고, 이것들을 조합해 새로운 그림을 만듭니다. 조합하는 방법은 smile 함수를 참고합니다.

import matplotlib as mpl
mpl.use("Agg")
import matplotlib.pyplot as plt
import elice_utils
import numpy as np
elice = elice_utils.EliceUtils()

def circle(P):
    return np.linalg.norm(P) - 1 # 밑의 코드와 동일하게 동작합니다.
    # return np.sqrt(np.sum(P * P)) - 1
    
def diamond(P):
    return np.abs(P[0]) + np.abs(P[1]) - 1
    
def smile(P):
    def left_eye(P):
        eye_pos = P - np.array([-0.5, 0.5])
        return np.sqrt(np.sum(eye_pos * eye_pos)) - 0.1
    
    def right_eye(P):
        eye_pos = P - np.array([0.5, 0.5])
        return np.sqrt(np.sum(eye_pos * eye_pos)) - 0.1
    
    def mouth(P):
        if P[1] < 0:
            return np.sqrt(np.sum(P * P)) - 0.7
        else:
            return 1
    
    return circle(P) * left_eye(P) * right_eye(P) * mouth(P)

def checker(P, shape, tolerance):
    return abs(shape(P)) < tolerance

def sample(num_points, xrange, yrange, shape, tolerance):
    accepted_points = []
    rejected_points = []
    
    for i in range(num_points):
        x = np.random.random() * (xrange[1] - xrange[0]) + xrange[0]
        y = np.random.random() * (yrange[1] - yrange[0]) + yrange[0]
        P = np.array([x, y])
        
        if (checker(P, shape, tolerance)):
            accepted_points.append(P)
        else:
            rejected_points.append(P)
    
    return np.array(accepted_points), np.array(rejected_points)

xrange = [-1.5, 1.5] # X축 범위입니다.
yrange = [-1.5, 1.5] # Y축 범위입니다.
accepted_points, rejected_points = sample(
    100000, #  점의 개수를 줄이거나 늘려서 실행해 보세요. 너무 많이 늘리면 시간이 오래 걸리는 것에 주의합니다.
    xrange, 
    yrange, 
    smile, # smile을 circle 이나 diamond 로 바꿔서 실행해 보세요.
    0.005) # Threshold를 0.01이나 0.0001 같은 다른 값으로 변경해 보세요.

plt.figure(figsize=(xrange[1] - xrange[0], yrange[1] - yrange[0]), 
           dpi=150) # 그림이 제대로 로드되지 않는다면 DPI를 줄여보세요.
           
plt.scatter(rejected_points[:, 0], rejected_points[:, 1], c='lightgray', s=0.1)
plt.scatter(accepted_points[:, 0], accepted_points[:, 1], c='black', s=1)

plt.savefig("graph.png")
elice.send_image("graph.png")

Leave a comment