본문으로 바로가기

이미지, 영상 -> 행렬로 표현
내가 어떤 데이터를 처리하고자 하고 그 데이터가 이미지라면 행렬로 바꾸는 작업을 가장 먼저 해야 한다.
행렬을 나타내려면 행렬을 나타내는 데이터 타입이 필요
python에서는 numpy가 행렬 데이터 타입을 제공해준다.
c or java는 행렬 데이터 타입이 없고 2차원 배열이 있음
2차원 배열은 행렬 연산을 하기 어려움

In [1]:
import numpy as np
In [2]:
A = np.array([1,2])
A
Out[2]:
array([1, 2])

numpy

  • vector / matrix 생성
  • 행렬 곱 (dot product)
  • broadcast
  • index / slice / iterator
  • useful function (loadtxt(), rand(), argmax(), ...)

선형대수학에서 벡터(vector) : 1차원 배열
행렬(matrix) : 2차원 배열
tensor : 3차원 이상 (matrix + channel)
-> tensorflow는 기본적으로 3차원 이상의 데이터를 다룬다.
기본적인 영상은 모두 tensor

numpy는 머신러닝 코드를 개발할 때 자주 사용되는 벡터, 행렬 등을 표현하고 연산할 때 반드시 필요한 라이브러리

numpy vs list

행렬의 특징 : 연산이 되어야 한다.
matrix를 나타내기 위해서는 list를 사용할 수도 있지만,
행렬 연산이 직관적이지 않고 오류 가능성이 높기 때문에, 행렬 연산을 위해서는 numpy 사용이 필수이다.
c / c++ / java에서도 2차원 배열을 만들 수 있다.
그러나 이를 통해 연산을 하기 위해서는 for문을 돌려야 한다.

In [3]:
A = [[1, 0], [0, 1]]
B = [[1, 1], [1, 1]]
A+B # 행렬 연산이 아닌 리스트 연산
Out[3]:
[[1, 0], [0, 1], [1, 1], [1, 1]]
In [4]:
A = np.array([[1, 0], [0, 1]])
B = np.array([[1, 1], [1, 1]])
A+B # 행렬 연산
Out[4]:
array([[2, 1],
       [1, 2]])
In [7]:
A = np.array([1, 2, 3])
B = np.array([4, 5, 6])

# vector A, B 출력
print("A :", A, ", B :", B)

# vector A, B 형상 출력 => shape
print("A.shape :", A.shape, ", B.shape :", B.shape)

# vector A, B 차원 출력 => ndim
print("A.ndim :", A.ndim, ", B.ndim :", B.ndim)
A : [1 2 3] , B : [4 5 6]
A.shape : (3,) , B.shape : (3,)
A.ndim : 1 , B.ndim : 1

shape을 반드시 알고 있어야 한다.
shape을 확인해야만 task 수행 가능.

In [8]:
# vector 산술 연산
print("A+B :", A+B)
print("A-B :", A-B)
print("A*B :", A*B)
print("A/B :", A/B)
A+B : [5 7 9]
A-B : [-3 -3 -3]
A*B : [ 4 10 18]
A/B : [0.25 0.4  0.5 ]
In [11]:
C = A.reshape(1, 3)
C.shape
Out[11]:
(1, 3)
In [12]:
C
Out[12]:
array([[1, 2, 3]])

shape
-> (row, column)tuple 출력
(3,) <- 1행의3열이 될 수도 있고, 3행의1열이 될 수도 있다는 의미.
numpy 내부적으로 유연성을 부여해주기 위해서 이렇게 쓴다.
그런데 내가 명시적으로 1행3열이라고 쓰고 싶으면 (1, 3)이라고 써야 한다.

In [13]:
C.ndim
Out[13]:
2

ndim
number of dimension 차원의 개수
어떤 데이터를 받았을 때 몇 차원인지 확인해봐야 할 일이 반드시 발생한다.
그때 ndim 사용

머신러닝 코드 구현시, 연산을 위해 vector, matix 등의 shape(형상), dimension(차원)을 확인하는 것이 필요하다.

In [15]:
A = np.array([1, 2, 3])
B = np.array([1, 2])
A+B
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-15-1d70737096d9> in <module>
      1 A = np.array([1, 2, 3])
      2 B = np.array([1, 2])
----> 3 A+B

ValueError: operands could not be broadcast together with shapes (3,) (2,) 

행렬의 shape이 다르면 연산을 할 수 없다. (일반적으로)

reshape(형변환)

행렬의 형상을 강제로 바꿔줘야 할 일이 많다.

In [17]:
A = np.array([[1, 2, 3], [4, 5, 6]])
B = np.array([[-1, -2, -3], [-4, -5, -6]])

# matrix A, B 형상 출력 => shape
print("A.shape :", A.shape, ", B.shape :", B.shape)

# matrix A, B 차원 출력 => ndim
print("A.ndim :", A.ndim, ", B.ndim :", B.ndim)
A.shape : (2, 3) , B.shape : (2, 3)
A.ndim : 2 , B.ndim : 2
In [18]:
# vector 생성
C = np.array([1, 2, 3])
In [20]:
A = np.array([[1, 2, 3, 4],
            [10, 20, 30, 40],
            [100, 200, 300, 400]])
print(A.shape)
(3, 4)
In [21]:
B = A.reshape(-1, 3) # 행은 상관없고 3열로 만들어라
In [22]:
print(B.shape)
print(B)
(4, 3)
[[  1   2   3]
 [  4  10  20]
 [ 30  40 100]
 [200 300 400]]
In [23]:
C = A.reshape(-1, 6) # 행은 상관없고 6열로 만들어라
print(C.shape)
print(C)
(2, 6)
[[  1   2   3   4  10  20]
 [ 30  40 100 200 300 400]]
In [24]:
D = A.reshape(-1, 5) # 행은 상관없고 5열로 만들어라
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-24-ad0435a18c10> in <module>
----> 1 D = A.reshape(-1, 5) # 행은 상관없고 5열로 만들어라

ValueError: cannot reshape array of size 12 into shape (5)

그런데 A가 5로 나눠떨어지지 않기 때문에 ValueError

numpy broadcast

행렬의 사칙연산은 기본적으로 두 개의 행렬 크기가 같은 경우에만 수행할 수 있다. 그러나 numpy에서는 특정 조건을 만족하면 크기가 다른 두 행렬 간에도 사칙연산 (+, -, *, /)을 할 수 있는데, 이를 브로드캐스트라고 지칭한다.
=> 차원이 작은 쪽이 큰 쪽의 행 단위로 반복적으로 크기를 맞춘 후에 계산한다.

In [25]:
A = np.array([[1, 2], [3, 4]])
b = 5
A+b
Out[25]:
array([[6, 7],
       [8, 9]])
In [26]:
C = np.array([[1, 2], [3, 4]])
D = np.array([4, 5])
C+D
Out[26]:
array([[5, 7],
       [7, 9]])
In [27]:
E = np.array([4, 5, 6])
C+E
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-27-888f56a6ce2a> in <module>
      1 E = np.array([4, 5, 6])
----> 2 C+E

ValueError: operands could not be broadcast together with shapes (2,2) (3,) 

예제

1-1

벡터와 행렬을 넘파이로 표현할 수 있는지?
손으로 먼저 풀어보고 넘파이로 결과를 확인해볼 것

In [29]:
A = np.array([1, 2]) # vector
B = np.array([3, 4])
T = np.array([5, 6])
print((1-A)*A)
print(T*(1-A)*A)
[ 0 -2]
[  0 -12]

1-2

In [31]:
C = np.array([[2], [3]]) # matrix
D = np.array([[4], [5]])
T = np.array([[7], [8]])
print((1-C)*C)
print(T*(1-C)*C)
[[-2]
 [-6]]
[[-14]
 [-48]]

(1-A)*A
앞으로 back propagation할 때 자주 사용된다.

행렬을 표시할 때 하나의 행은 반드시 []로 묶여 있어야 한다.

시그마는 평균을 내포하고 있다.
시그마의 수학적 의미 : 모든 값을 더하여 평균으로 나눈 뒤 임의의 값을 곱한 것

y = 3x+4
y = 3x+4*1 x와 1이라는 입력이 들어가서 각각 3, 4가 곱해진 후 이 둘을 더하여 출력한다.

데이터 관점에서의 곱하기 operation?
31 = 3 그대로 내보낸다.
3
0 = 0 없애버린다. 3*2 = 6 데이터에 변형을 가한다.

더하기 operation?
평균이라는 것에다가 변화를 주는 것.
평균이라는 개념이 이미 그 공식에 포함되어 있다.
데이터들을 평균으로 나눈 뒤 특정 값을 곱해준 것의 합

그 공식이 뭘 의미하는지 인사이트를 체크

행렬의 사칙연산은 (일반적으로) 크기가 같아야 한다.

numpy 행렬 곱(dot product)

내적
A dot product B를 할 때는 행렬 A의 열 벡터와 B의 행 벡터가 같아야 계산된다. 만약 같지 않다면 reshape 또는 전치 행렬 (transpose) 등을 사용하여 형변환을 한 후에 행렬 곱을 실행해야 한다.

행렬 곱이 왜 필요한가?
행렬곱이 어디에 쓰이는지부터 생각해보자.
그림판에 기본으로 들어가 있는 것이 행렬곱 연산이다.
곱하기와 더하기의 오모한 조합으로 인해서 사용된다.
더하기가 있다는 건 평균의 성질이 있다는 뜻이다.
행렬곱은 많은 물리적 의미를 내포하고 있다.
행렬곱 : 곱하고 더하는 것
곱하기 : 원래의 데이터에 variation을 주고 있는 것

다시, 행렬 곱이 왜 필요??
행렬을 계산할 때 사칙연산이 존재.
사칙연산의 특징은 계산하려는 행렬의 크기가 같아야 한다는 점이다.
내가 어떤 데이터를 더하고 싶은데 더하고자 하는 행렬이 200*200이면 뒤에도 반드시 200*200 행렬이 들어와야 사칙연산이 가능하다.
즉 입력으로 들어오는 행렬에 맞춰서 크기를 맞춰줘야 한다.

행렬곱을 이용하면 더하는 크기에 상대적으로 자유로워진다.
만약 어떤 이미지에 변형을 주고 싶은데 그 이미지가 6000*100이라고 할 때, 행렬곱을 이용하면 가운데만 맞춰주면 Ok
(6000*100)*(100*500)*(500*100)
결과적으로 6000*100 행렬이 나온다.
중간에 어떤 연산을 하든지 간에!

만약 행렬곱이 없다고 하면 6000*100 연산을 계속 해줘야 한다.
다양한 데이터의 조합을 통해 내가 원하는 걸 만들어낼 수 있다!

내가 그림판에서 숫자를 하나 썼으면 지우개로 이걸 지울 수 있다.
-> 행렬곱에서 0을 곱하는 것
원본 데이터 크기로부터 자유로워질 수 있다는 뜻이다.

행렬곱을 통해서 그림판에서 이미지 이펙트 주고 이런 것이 가능 (다는 아니고 주로 행렬곱을 이용한다.)
위 식에서 (100*500)이 필터라고 해 보면 이 이미지에 대해 필터 적용할 수 있는 것이 된다.
원본 데이터에 상관없이 그런 효과를 줄 수 있는 행렬을 찾는 것도 수학의 한 분야 -> 필터를 잘 만드는 회사가 돈을 잘 번다.

행렬 곱 : 각각의 곱한 것을 더한다.
곱한다는 건 원본 데이터를 튜닝하는 것, 뭔가 이펙트를 주고 있다는 것.
더하는 것 : 각 효과를 준 것을 평균내면 우리가 원하는 결과를 만들어낸다.

이 중간의 행렬을 나중에 전문용어로 필터라고 한다.
필터를 잘 만드는 회사들이 돈을 잘 번다.
CNN에서 보면 필터를 적용을 많이 해야 하는데 필터는 이미 이미지에 무슨 필터를 적용하면 무슨 효과가 나온다는 게 다 증명되어 있다.

공식만 푸는 건 아무 의미가 없다.
그 공식이 나타내는 물리적 의미를 파악해야.

In [60]:
A = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
B = np.array([[-1, -2], [-3, -4], [-5, -6]]) # (3, 2)

예제

2-1

In [40]:
X = np.array([[1, 2], [3, 4]])
W = np.array([[0, 1, 1], [3, 2, 1]])
T = np.array([[1, 0], 0, 2])
b = -1
Y = np.dot(X, W) + b
print(Y)
E = T - Y
[[ 5  4  2]
 [11 10  6]]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-404dfdaa3eb9> in <module>
      5 Y = np.dot(X, W) + b
      6 print(Y)
----> 7 E = T - Y

TypeError: unsupported operand type(s) for -: 'list' and 'int'

T와 Y의 형상이 다르기 때문에 TypeError

전치행렬 (transpose)

행과 열을 바꿔야 하는 경우
1) file로부터 데이터를 읽어들여서 내가 뭔가 계산해야 할 때
어떤 파일을 table형태로 불러들임
2) 데이터 정규화할 때

normalization할 때 반드시 해야 하는 것이 transpose이다.
수학적으로 아무 문제가 없는데 문제가 되는 것이
프로그래밍할 때 이걸 안 하면 코드가 쓰레기가 되는 코드가 있음

어떤 입력으로 받는 데이터가 7000, 8400, 100, 3 네 개의 데이터가 있고 출력이 e^x가 나올 때 수학적으로는 어떤 데이터가 입력으로 들어가든 아무 문제가 없다.
그런데 얘를 프로그래밍하면 그때 문제가 발생한다.
입력 8400에 대해서 이를 하면 프로그래밍에는 데이터의 크기에 제한이 있기 때문에 무한대값이 나와 버린다. 프로그래밍적으로 이 값을 계산할 수 없다. 즉 이 데이터를 담아둘 수 있는 변수가 없게 되는 것이다.

이때 필요한 게 지금 말한 normalization이다. => 데이터의 특성은 그대로 둔 채 크기를 작게 하거나 크게 하거나 축을 변화시키는 것! 세 개의 입력 데이터 3, 100, 8400이 있다고 할 때 이들의 관계는 3<100<8400이고, 데이터에 어떤 변화를 가지더라도 이 관계는 유지되어야 한다.
가장 쉬운 것은 최댓값으로 나누는 것이다.

3/8400, 100/8400, 8400/8400
=> 모든 데이터는 0과 1 사이에 있게 된다.

모든 데이터가 0과 1 사이에 있지만 데이터의 특성은 그대로 있다.
이 값들을 넣으면 데이터가 깔끔해진다. 계산 결과값에 최댓값을 다시 곱해주면 ok

수학공식을 그대로 프로그래밍할 수는 없다.
그래서 수치프로그래밍이 프로그래밍의 꽃이다.

최댓값으로 나누는 것은 가장 원시적인 정규화 방법이다.
이 방법의 문제점은 최댓값이 0인 경우 divisionbyzero error가 발생한다는 점이다.
그렇다면 다음과 같이 최댓값이 0인 경우 어떻게 할까?
-7, -6, 0

이런 경우 최댓값, 최솟값을 구한다.
min < data < max
하나 이상의 데이터가 있는 경우 어떤 데이터이든지 최댓값과 최솟값 사이에 있다.
min-min < data-min < max-min
0 < data-min < max-min
0 < (data-min)/(max-min) < 1
이 경우 모든 값이 0과 1 사이에 있게 된다.
그런데 우리가 유의해야 할 점은 나누는 값이 마이너스가 될 경우 부등호가 바뀐다는 점이다.

i) max가 양수인 경우 당연히 이 부등호의 우변은 양수 -> 나눠줘도 부등호가 바뀌지 않는다.  
ii) max가 0인 경우 -> min은 마이너스라는 얘기 -> 나눠줘도 부등호가 바뀌지 않는다.  
iii) max가 음수인 경우 -> 음수 - 더 작은 음수는 양수 -> 나눠줘도 부등호가 바뀌지 않는다.  

=> 최댓값이 0인 경우에도 모든 데이터를 0과 1 사이로 만들어줄 수 있는 general한 formular

머신러닝에서 모든 데이터는 0과 1 사이로 표현해야 한다.
정규화를 안 하면 아주 간단한 데이터는 할 수 있어도 큰 데이터로 가게 되면 문제가 생김!
반드시 해야 한다.

In [41]:
A = np.array([[1, 2], [3, 4], [5, 6]]) # (3, 2)
B = A.T # A의 전치행렬 (2, 3)
print(A.shape, B.shape)
print(A)
print(B)
(3, 2) (2, 3)
[[1 2]
 [3 4]
 [5 6]]
[[1 3 5]
 [2 4 6]]
In [42]:
# vector 전치행렬
C = np.array([1, 2, 3, 4, 5]) # not matrix
D = C.T # C는 vector이 므로 transpose 안됨
E = C.reshape(1, 5) # (1, 5) matrix
F = E.T # E의 전치행렬

print(C.shape, D.shape)
print(E.shape, F.shape)
print(F)
(5,) (5,)
(1, 5) (5, 1)
[[1]
 [2]
 [3]
 [4]
 [5]]

행렬은 전치행렬이 가능하다.
그러나 vector는 행렬이 아니기 때문에 전치행렬이 불가능하다!
그런데 이때 에러가 나지 않기 때문에 실수를 많이 한다.
우리가 보기에는 벡터 C가 1행 5열이라고 생각하고 전치시켜서 5행 1열로 바뀌었다고 생각하지만 실제로는 벡터이기 때문에 전치되지 않았다.
전치행렬은 반드시 행렬에 대해서만 가능하다.
벡터를 transpose시키려면 반드시 reshape하고 transpose해야 한다.

In [43]:
A = np.array([[1, 2, 3], [4, 5, 6]])
B = np.array([10, 20, 30])
C = A.T
D = np.dot(C, B)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-43-ff8f5e5400a0> in <module>
      2 B = np.array([10, 20, 30])
      3 C = A.T
----> 4 D = np.dot(C, B)

ValueError: shapes (3,2) and (3,) not aligned: 2 (dim 1) != 3 (dim 0)
In [46]:
A = np.array([[2, 2], [1, 1]])
B = np.array([1, 2])
A.dot(B)
Out[46]:
array([6, 3])
In [47]:
B.shape
Out[47]:
(2,)

B는 (1, 2)로도, (2, 1)로도 해석할 수 있다는 파이썬의 배려이다.
그래서 dot으로 하게 되면 우리가 보기에는 벡터인데 내부적으로 이를 행으로 인식해서 연산한다.

In [48]:
B.dot(A)
Out[48]:
array([4, 4])

지금은 B의 2를 열로 인식하여 연산한 것이다.
그러나 웬만하면 명시적으로 바꿔서 쓰는 것이 좋다. 왜냐하면 나중에 역전파 코딩할 때 trace해가며 오류 잡기가 굉장히 어렵다.
일관되게 하되, 강사님은 행렬로 바꿔서 하는 것을 추천

numpy 행렬의 indexing과 slicing

넘파이 array는 행렬, 즉 행과 열이 있다.
하나의 열과 행은 list처럼 보인다.
그래서 list의 속성을 그대로 가지고 있다. - indexing과 slicing

In [49]:
A = np.array([10, 20, 30, 40, 50, 60]).reshape(3,2)
print("A.shape ==", A.shape)
print(A)
A.shape == (3, 2)
[[10 20]
 [30 40]
 [50 60]]
In [50]:
print("A[0, 0] ==", A[0, 0])
print("A[2, 1] ==", A[2, 1])
A[0, 0] == 10
A[2, 1] == 60
In [51]:
print("A[0:-1, 1:2] ==", A[0:-1, 1:2])
A[0:-1, 1:2] == [[20]
 [40]]
In [57]:
print("A[:, 0] ==", A[:, 0])
print("A[:, :] ==", A[:, :])
A[:, 0] == [10 30 50]
A[:, :] == [[10 20]
 [30 40]
 [50 60]]

데이터를 하나만 뽑을 때는 숫자로 출력해준다.
범위를 지정해서 뽑을 때는 괄호로 묶여서 출력된다.

예제 3 - 1~3

In [59]:
X = np.array([7, 5, 3, 1, 1, 2, \
              3, 4, 4, 0, 2, 8]).reshape(3, 4)
A = X.reshape(-1, 3)
print("A :", A)
B = A[0:2, 1:2]
print("B :", B)
C = A[1:, :-1]
print("C :", C)
A : [[7 5 3]
 [1 1 2]
 [3 4 4]
 [0 2 8]]
B : [[5]
 [1]]
C : [[1 1]
 [3 4]
 [0 2]]

numpy - iterator

내일 미분할 때 반드시 나오는 코드
slicing은 어느 특정 범위를 지정해서 뽑아내는 것이다.
어떤 행렬 A가 있을 때 특정 값을 뽑아오고 싶다면 특정 위치를 지정하거나 범위를 지정하여 접근할 수 있다. 또 다른 방법으로 iterator가 있다. (이 이외에도 방법은 많지만 머신러닝에서는 이 세 가지만 알면 아무 문제 없다.)
iterator는 언제 쓰이는 것일까?
데이터를 처음부터 끝까지 차례대로 가져오고 싶을 때 사용한다.
slicing은 모든 행에 대하여 모든 열이라고 하면 데이터를 통으로 가져오는 것
iterator는 하나씩, 처음부터 끝까지 가져온다.
iterator는 모든 프로그래밍 언어에 다 있다.
C++ -> pointer
java -> reference
통으로 가져오는 것이 아니라 하나씩! 한번에 통으로 가져오는 게 아니고 하나씩 가져올 필요가 있다. (나중에)

처음을 가리키고 하나씩 증가시켜주기 위해서 while문이 동작한다.

In [66]:
A = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
print(A, '\n')

# 행렬 A의 iterator 생성
# it는 내부적으로 pointer로 되어 있다.
it = np.nditer(A, flags=['multi_index'],\
               op_flags=['readwrite'])

while not it.finished:
    # it가 가리키는 index를 가지고 와서
    idx = it.multi_index
    print('index = ', idx, ', type(idx) = ', type(idx),\
          ', value = ', A[idx])
    it.iternext() # 다음으로 넘어간다.
[[10 20 30 40]
 [50 60 70 80]] 

index =  (0, 0) , type(idx) =  <class 'tuple'> , value =  10
index =  (0, 1) , type(idx) =  <class 'tuple'> , value =  20
index =  (0, 2) , type(idx) =  <class 'tuple'> , value =  30
index =  (0, 3) , type(idx) =  <class 'tuple'> , value =  40
index =  (1, 0) , type(idx) =  <class 'tuple'> , value =  50
index =  (1, 1) , type(idx) =  <class 'tuple'> , value =  60
index =  (1, 2) , type(idx) =  <class 'tuple'> , value =  70
index =  (1, 3) , type(idx) =  <class 'tuple'> , value =  80

numpy useful function(1)

In [61]:
'''
파일을 읽어들일 때 load_text 사용
'''

# dtype 반드시 명시
# delimiter : 분리자
loaded_data = np.loadtxt('./data-01.csv', delimiter=',', \
                        dtype=np.float32)

print(type(loaded_data))
print(loaded_data.shape)

# 보통 training data에서 입력과 정답 분리할 때 이렇게 코딩
# 일반적으로 정답은 맨 끝 열 혹은 맨 앞 열에 있음
# 그래서 모든 행에 대해서 [0] 또는 [-1] 값을 가져오는 것
x_data = loaded_data[:, 0:-1]
t_data = loaded_data[:, [-1]] # target -> 정답값

# 데이터 차원 및 shape 확인
print("x_data.ndim = ", x_data.ndim, ", x_data.shape = ", x_data.shape)
print("t_data.ndim = ", t_data.ndim, ", t_data.shape = ", t_data.shape)
<class 'numpy.ndarray'>
(25, 4)
x_data.ndim =  2 , x_data.shape =  (25, 3)
t_data.ndim =  2 , t_data.shape =  (25, 1)

numpy useful function(2)

기본적으로 제공되는 학습 데이터들 중 대표적인 것은
당뇨병 예측 데이터 (795, 8)
심장병 예측 데이터 (8000, 160)
등이 있다. 어떤 의미있는 데이터를 머신러닝으로 예측하고 싶으면 기본적으로 100만 개의 데이터가 있어야 한다.
이 데이터를 구하는 것이 너무 어렵다.

머신러닝은 수학공식을 코드화시키는 작업이 많다.

np.random.rand(shape)

머신러닝에서 가중치와 bias가 필요한데, 이때 임의의 값을 세팅해야 한다.
learning(학습) -> 뭘 학습하는 것? 가중치w와 바이어스b
머신러닝의 최종 목표 : 최적의 가중치바이어스를 찾는 것!

In [67]:
# 0~1 사이의 random number 발생
random_number1 = np.random.rand(3) # vector 생성
random_number2 = np.random.rand(1, 3)
random_number3 = np.random.rand(3, 1)

print("random_number1 == ", random_number1, \
      ", random_number1.shape == ", random_number1.shape)
print("random_number2 == ", random_number2, \
      ", random_number2.shape == ", random_number2.shape)
print("random_number3 == ", random_number3, \
      ", random_number3.shape == ", random_number3.shape)
random_number1 ==  [0.15363246 0.31879385 0.38095474] , random_number1.shape ==  (3,)
random_number2 ==  [[0.43114241 0.91113111 0.49429761]] , random_number2.shape ==  (1, 3)
random_number3 ==  [[0.60381381]
 [0.95729628]
 [0.86062632]] , random_number3.shape ==  (3, 1)

np.sum(...), np.exp(...), np.log(...)

In [68]:
X = np.array([2, 4, 6, 8])

print("np.sum(X) ==", np.sum(X))
print("np.exp(X) ==", np.exp(X))
print("np.log(X) ==", np.log(X))
np.sum(X) == 20
np.exp(X) == [   7.3890561    54.59815003  403.42879349 2980.95798704]
np.log(X) == [0.69314718 1.38629436 1.79175947 2.07944154]

넘파이를 쓸 때는 웬만하면 for문을 쓰지 않는다.
위의 task를 for문을 돌려서도 수행 가능하지만 그러면 낭비!

np.max(...), np.min(...), np.argmax(...), np.argmin(...)

argmax는 최댓값이 있는 곳의 index를 return
argmin은 최솟값이 있는 곳의 index를 return

어디서 쓸까?
분류 문제에서 output이 원핫인코딩 vector로 나오면
0.99가 있는곳의 index가 5라면 argmax 써서 정답을 5로 판단한다.

거의 행 기준으로 최댓값을 뽑는다. (axis=1)

In [72]:
X = np.array([2, 4, 6, 9])
print("np.max(X) ==", np.max(X))
print("np.min(X) ==", np.min(X))
# argmax, argmin은 one hot encoding할 때 아주 많이 사용한다.
# 특히 argmax
print("np.argmax(X) ==", np.argmax(X))
print("np.argmin(X) ==", np.argmin(X))
np.max(X) == 9
np.min(X) == 2
np.argmax(X) == 3
np.argmin(X) == 0
In [73]:
X = np.array([[2, 4, 6], [1, 2, 3], [0, 5, 8]])

print("np.max(X) == ", np.max(X, axis=0)) # axis=0, 열 기준
print("np.min(X) == ", np.min(X, axis=0)) # axis=0, 열 기준

print("np.max(X) == ", np.max(X, axis=1)) # axis=1, 행 기준
print("np.min(X) == ", np.min(X, axis=1)) # axis=1, 행 기준

print("np.argmax(X) == ", np.argmax(X, axis=0)) # axis=0, 열 기준
print("np.argmax(X) == ", np.argmin(X, axis=0)) # axis=0, 열 기준

print("np.argmax(X) == ", np.argmax(X, axis=1)) # axis=0, 행 기준
print("np.argmax(X) == ", np.argmin(X, axis=1)) # axis=0, 행 기준
np.max(X) ==  [2 5 8]
np.min(X) ==  [0 2 3]
np.max(X) ==  [6 3 8]
np.min(X) ==  [2 1 0]
np.argmax(X) ==  [0 2 2]
np.argmax(X) ==  [2 1 1]
np.argmax(X) ==  [2 2 2]
np.argmax(X) ==  [0 0 0]

np.ones(...), np.zeros(...)

In [74]:
A = np.ones([3, 3])
print("A.shape == ", A.shape, ", A ==", A)

B = np.zeros([3, 2])
print("B.shape == ", B.shape, ", B ==", B)
A.shape ==  (3, 3) , A == [[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
B.shape ==  (3, 2) , B == [[0. 0.]
 [0. 0.]
 [0. 0.]]

예제3 4~6

In [78]:
X = np.array([7, 5, 3, 1, 1, 2, \
              3, 4, 4, 0, 2, 8]).reshape(3, 4)
A = X.reshape(-1, 3)
print("A :", A)
B = A[0:2, 1:2]
print("B :", B)
C = A[1:, :-1]
print("C :", C)
D = np.argmax(A, 1)
print(D)
E = C.reshape(-1, 6) # matrix
print(E)
F = np.sum(E)
print(F)
A : [[7 5 3]
 [1 1 2]
 [3 4 4]
 [0 2 8]]
B : [[5]
 [1]]
C : [[1 1]
 [3 4]
 [0 2]]
[0 2 1 2]
[[1 1 3 4 0 2]]
11

matplotlib, scatter plot

실무에서는 머신러닝 코드를 구현하기 전에, 입력 데이터의 분포와 모양을 먼저 그래프로 그려보고, 데이터의 특성과 분포를 파악한 후 어떤 알고리즘을 적용할지 결정한다.
데이터 시각화를 위해서 사용하는 가장 기본적인 파이썬 라이브러리 - matplotlib
일반적으로 line plot, scatter plot 등을 통해 데이터의 분포와 형태를 파악한다.

In [90]:
import matplotlib.pyplot as plt

%matplotlib inline

# x_data, y_data 생성
x_data = np.random.rand(100)
y_data = np.random.rand(100)

plt.title('scatter plot')
plt.grid()
# 'o' : 직선이 아니라 점으로 찍겠다.
plt.scatter(x_data, y_data, color='b', marker='o')
plt.show()

모든 데이터가 0~1 -> 정규화할 필요 없다.
activation function을 쓸 때 sigmoid를 써도 된다는 insight를 얻을 수 있다.

In [92]:
x_data = [x for x in range(-5, 5)]
y_data = [y*y for y in range(-5, 5)]

# 어떻게 변하는지 패턴을 분석해보고 싶으면 line으로 바꾸면 ok
plt.title('line plot')
plt.grid()
plt.plot(x_data, y_data, color='b')
plt.show()

이 데이터가 어떻게 생겼는지 파악을 반드시 해야 하는 경우가 이 과정에서 딱 두 번 나온다. 비행기 이륙 거리, 착륙 거리 파악할 때 반드시 파악해야만 딥러닝에서 선형 회귀를 쓸 수 있다. 그래프를 그려 보면 선형 회귀를 써야 한다는 사실을 알 수 있다.

In [1]:
from IPython.core.display import display, HTML
display(HTML("<style>.container {width:90% !important;}</style>"))
In [ ]:
 

190928 numpy, matplotlib


'교육 및 강연 > 인공지능 기초' 카테고리의 다른 글

4일차 190929 - 수치미분  (0) 2019.09.30
2일차 - 190921 python  (0) 2019.09.30