이번 포스팅에서는 파일 형태의 이미지 데이터를 TensorFlow와 Keras 메소드를 사용하여


(1) 웹에서 압축 이미지 파일을 다운로드하여 압축을 해제하고,

(2) 폴더 이름을 확인하고 폴더 밑에 있는 파일 이름을 리스트로 만들기

(3) 이미지 파일을 로딩하여 시각화하고 라벨 이름 출력하기


를 해보겠습니다.



  (1) 웹에서 압축 이미지 파일을 다운로드하여 압축 해제하기


먼저 TensorFlow 와 객체지향 파일시스템 경로(object-oriented filesystem paths)를 관리할 때 사용하는 pathlib 모듈을 importing 하겠습니다.



import tensorflow as tf

import pathlib

import os


print(tf.__version__)

[Out] 2.4.0-dev20200913

 



이제 준비가 되었으니 https://storage.googleapis.com 의 예제 이미지로 압축되어 저장되어 있는 'flower_photos.tgz' 압축 파일을 Keras의 get_file() 메소드를 사용하여 가져와서 압축을 풀어(untar = True)보겠습니다.



flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)




이러면 압축이 풀린 파일이 '.keras/datasets/flower_photos 라는 폴더에 저장되어 있습니다. 이를 pathlib.Path() 메소드로 Mac, Linux, Windows OS에 상관없이 경로를 인식할 수 있도록 PurePath로 만들어주었습니다.


* source: https://docs.python.org/3/library/pathlib.html




print(flowers_root)

[Out] /Users/xxx/.keras/datasets/flower_photos


flowers_root = pathlib.Path(flowers_root)

flowers_root

[Out] PosixPath('/Users/xxx/.keras/datasets/flower_photos')

 



저는 Mac OS 에서 작업을 했는데요, Mac OS에서 숨겨진 폴더, 파일을 보려면 Finder 에 커서를 가져다대고 'Shift + Command + .' 을 동시에 눌러주면 됩니다. 그러면 아래의 화면 캡쳐처럼 '/.keras/datasets/flower_photos/' 경로에 다운로드 후 압축해제한 이미지 파일들이 들어있음을 눈으로 직접 확인할 수 있습니다.






  (2) 폴더 이름을 확인하고 폴더 밑에 있는 파일 이름을 리스트로 만들기


다음으로, root directory 밑에 있는 하위 디렉토리와 파일 이름들을 모두(flowers_root.glob("*") ) 가져다가 for loop 으로 순회하면서 하나씩 차례대로 출력해서 확인해보겠습니다.


위의 화면캡쳐에서 숨겨진 폴더, 파일을 눈으로 봤던 것과 동일하게 하위 폴더에는 'roses', 'sunflowers', 'daisy', 'dandelion', 'tulips' 폴더와 'LICENSE.txt' 텍스트 파일이 들어있군요.



# The root directory contains a directory for each class.
for item in flowers_root.glob("*"):
    print(item.name)


[Out]

roses sunflowers daisy dandelion tulips LICENSE.txt




이제 TensorFlow.Dataset.list_files() 메소드를 사용해서 각 꽃 이름 폴더 ('roses', 'sunflowers', 'daisy', 'dandelion', 'tulips' 폴더) 밑에 있는 이미지 파일 이름(jpg 포맷) 들의 리스트를 만들어보겠습니다.


그리고 이중에서 3개만 가져와서(take(3)) 하나씩 차례대로 '전체 경로/파일이름'을 출력해보겠습니다.



# The files in each class directory are examples:
list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))


for f in list_ds.take(3):
    print(f.numpy())


b'/Users/xxx/.keras/datasets/flower_photos/daisy/4440480869_632ce6aff3_n.jpg' b'/Users/xxx/.keras/datasets/flower_photos/daisy/1879567877_8ed2a5faa7_n.jpg' b'/Users/xxx/.keras/datasets/flower_photos/sunflowers/969913643_9d5cd2fe45_m.jpg'

 




  (3) 이미지 파일을 로딩하여 시각화하고 라벨 이름 출력하기


이제 이미지 파일이 준비가 되었으니 TensorFlow.keras.preprocessing.image 메소드를사용하여 꽃 이미지 3개를 크기가 (250, 250)인 이미지로 변환하여 로딩하고, matplotlib 모듈로 시각화(plt.imshow(img))해보겠습니다. ( tf.Tensor() 파일을 f.numpy() 로 변환하여 로딩)


이때 TensorFlow.strings.split(f, os.sep)[-2] 로 '전체 경로/파일이름' 리스트에서 뒤에서 부터 두번째에 위치한 라벨을 파싱해서 가져와서 이미지를 시각화할 때 라벨도 같이 출력을 해보겠습니다.


해바라기(sunflowers), 장미(roses), 민들레(dandelions) 이 차례대로 시각화되었네요.



from tensorflow.keras.preprocessing import image
import matplotlib.pyplot as plt

for f in list_ds.take(3):
    img = image.load_img(f.numpy(), target_size=(250, 250))
    label = tf.strings.split(f, os.sep)[-2]
    print(label.numpy())
    plt.imshow(img)
    plt.show()




* Reference: https://www.tensorflow.org/guide/data?hl=en



많은 도움이 되었기를 바랍니다.

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.



728x90
반응형
Posted by Rfriend
,

R 의 가장 대표적인 데이터 자료 구조 중의 하나를 꼽으라고 하면 행(row)과 열(column)로 구성된 2차원 배열 형태의 테이블인 Data Frame 일 것입니다. 

 

그리고 Data Frame 자료 구조를 조작, 관리, 전처리 할 수 있는 패키지로는 Base R과 dplyr 패키지가 많이 사용이 되는데요, 이번 포스팅에서는 Data Frame의 확장 데이터 구조인 data.table 데이터 구조와 이를 조작, 관리, 처리하는데 사용하는 data.table 패키지에 대해서 소개하려고 합니다. 

 

(1) R data.table은 무엇인가? (What is R data.table?)

(2) 왜 R data.table 인가? (Why R data.table?)

(3) R data.table 설치하기 (How to install R data.table)

(4) R data.table 참고할 수 있는 사이트 (Reference sites of R data.table)

(5) R data.table 구문 소개 포스팅 링크

 

 

  (1) R data.table 은 무엇인가? (What is R data.table?)

 

R data.table 은 (a) 행과 열로 구성된 2차원의 테이블 형태를 가지는 'data.frame'의 확장 데이터 구조(data.table: Extension of 'data.frame')이면서, (b) data.table을 생성, 조작, 처리, 집계할 수 있는 패키지(package) 입니다. 

 

아래에 data.table 패키지를 사용하여 문자열을 가진 'g' 변수와 숫자형 데이터를 가진 'x' 변수에 대해 10개의 관측치를 가지고 있는 간단한 data.table 자료구조를 하나 만들어 봤는데요, str() 로 자료 구조에 대해 확인해보면 'Classes 'data.table' and 'data.frame': 10 obs. of 2 variables:' 라는 설명이 나옵니다. 'data.table'이면서 'data.frame'이라고 나옵니다. 

 

View(data_dt) 로 자료를 확인해봐도 기존에 우리가 알고 있던 data.frame 과 data.table 이 구조가 같다는 것을 눈으로 확인할 수 있습니다. 

 

 

library(data.table)

 

> data <- data.table(g = c(rep('a', 5), rep('b', 5)), x = 1:10)

> str(data)

Classes 'data.table' and 'data.frame': 10 obs. of  2 variables:

 $ g: Factor w/ 2 levels "a","b": 1 1 1 1 1 2 2 2 2 2

 $ x: int  1 2 3 4 5 6 7 8 9 10

 - attr(*, ".internal.selfref")=<externalptr>

 

 

 

> View(data)

 

 

 

R CRAN에는 data.table 패키지에 대해서, 

  • 대용량 데이터(예: 100GB)에 대한 빠른 집계
    (Fast aggregation of large data (e.g. 100GB in RAM))
  • 빠른 정렬된 조인
    (Fast ordered joins)
  • 복사를 사용하지 않고 그룹별로 칼럼에 대한 빠른 합치기/수정/삭제
    (Fast add/modify/delete of columns by group using no copies at all)
  • 사용자 친화적이고 빠른 문자 구분 값의 읽기와 쓰기
    (Friendly and fast character-separated-value read/write)
  • 빠른 개발을 위한 자연스럽고 유연한 구문을 제공
    (Offers a natural and flexible syntax, for faster development.)

한다고 소개하고 있습니다. 

 

 

 

  (2) 왜 R data.table 인가? (Why R data.table?)

 

data.table 의 GitHub 페이지에서 '왜 data.table 인가 (Why data.table?)' 에 대해서 6가지 이유를 말하고 있습니다. (위의 data.table 소개 내용과 유사합니다만, 이유 항목별로 순서가 조금 다르고, 항목도 조금 다르긴 합니다.)

  • 빠르게 쓰고 읽을 수 있는 간결한 구문 (concise syntax: fast to type, fast to read)
  • 빠른 속도 (fast speed)
  • 효율적인 메모리 사용 (memory efficient)
  • 세심한 API 라이프사이클 관리 (careful API lifecycle management)
  • 커뮤니티 (community)
  • 풍부한 기능 (feature rich)

 

첫번째 data.frame 이 좋은 이유로 "빠르게 쓰고 읽을 수 있는 간결한 구문 (concise systax: fast to type, fast to read)"을 들었는데요, 이에 대해서는 Base R, dplyr, data.table 의 세 개 패키지별로 위의 data 에 대해서 그룹 'g' 별로 변수 'x'의 평균(mean) 을 구해보는 구문을 비교해보면 이해하기 쉬울 것 같습니다. 

 

자, 아래 구문을 보면 어떠신가요? 제 눈에는 data.table 이 제일 간결해보이네요!

 

 Base R

dplyr 

data.table 

 tapply(

    data$x, 

    data$y, 

    mean)

 data %>%

    group_by(g) %>%

    summarise(mean(x))

 data[, mean(x), by = 'g']

 

 

두번째 data.frame 의 장점으로 "빠른 속도 (fast speed)" 를 들었는데요, H2Oai 에서 R data.table과 R dplyr 뿐만 아니라 Python pandas, Spark, Julia 등 다른 Database-like 언어의 패키지까지 벤치마킹 테스트를 해놓은 자료가 있어서 아래에 소개합니다. 

 

질문은 5GB 데이터셋에 대해 'id1' 그룹별로 'v1' 칼럼에 대해 합(sum)을 구하라는 집계를 수행하는 것인데요, R data.table 이 12초, R dplyr은 156초, Python pandas는 100초가 걸렸네요. data.table이 겁나게 빠른 것을 알 수 있습니다. 

 

 

* 출처: https://h2oai.github.io/db-benchmark/

 

data.table이 이처럼 빠른데는 여러가지 이유가 있는데요, 그중에서도 핵심은 data.table은 테이블을 수정할 때 레퍼런스(Reference)를 이용할 수 있으므로, 새로운 객체를 다시 만들 필요없이, 원래의 객체를 바로 수정할 수 있다는 점입니다.  

 

 

세번째 data.table의 장점으로 "효율적인 메모리 사용 (memory efficient)"을 꼽았는데요, 역시 H2O ai 에서 위와 동일 질문에 대해 이번에는 '50GB' 의 대용량 데이터에 대해서 수행해보았습니다. 

 

특기할 점은 R data.table은 122초, Spark은 374초가 걸려서 에러없이 수행이 된 반면, R dplyr, Python pandas는 "Out of Memory"가 났다는 점입니다. 

 

* 출처: https://h2oai.github.io/db-benchmark/

 

 

 

네번째로 data.table의 장점으로 "세심한 API 라이프사이클 관리(careful API lifecycle management)"를 들고 있습니다. 

 

data.table 패키지는 Base R에만 의존성(dependency)을 가지고 있습니다. 폐쇄망 환경에서 R 패키지 설치하거나 버전 업그레이드 할 때 의존성 확인하면서 하나 하나 설치하려면 보통 일이 아닌데요, data.table 패키지는 Base R에만 의존성이 있으므로 패키지 설치 및 관리가 무척 수월하겠네요. 

 

 

다섯째 data.table의 장점으로 "커뮤니티 (Community)"를 들고 있습니다. data.table 패키지는 Matt Dowle 외 100여명의 contributor 가 제작 및 관리하고 있는 패키지로서, 소스코드는 https://github.com/Rdatatable/data.table 에서 확인하거나 소스 코드에 기여할 수 있습니다. 

 

 

여섯째 data.table의 장점으로 "풍부한 기능 (feature rich)"을 들고 있는데요, 앞으로 하나씩 같이 알아가보시지요. 

 

 

이렇게 data.table 은 굉장히 매력적인 장점을 가지고 있는데요, 한가지 단점이 있다면 기존에 익숙한 Base R, dplyr 을 놔두고 새로 data.table 패키지의 구문을 배워야 한다는 점입니다. 

 

Base R 쓰다가 dplyr 을 처음 봤을 때의 당혹스러움만큼이나 data.table 구문을 처음 보면 아마도 당황스러울 것입니다. '어, 이게 R 맞나? 내가 지금 무슨 프로그래밍 언어를 보고 있는 거지?' 하고 말이지요. 

 

그래도 다행이라면 구문이 간결해서 배우기 (상대적으로) 쉽다는 점이겠습니다. R data.table은 충분히 시간과 노력을 투자해서 배워볼 만한 값어치가 있다고 생각합니다. 

 

 

 

  (3) R data.table 설치하기 (How to install R data.table)

 

패키지 설치 및 로딩은 다른 패키지와 동일합니다. 

 

 

# install data.table package

install.packages("data.table")

 

# latest development version:

data.table::update.dev.pkg()

 

# loading data.table package

library(data.table)

 

 

 

 

  (4) R data.table 참고할 수 있는 사이트 (Reference sites of R data.table)

 

- data.table 패키지 소소코드: https://github.com/Rdatatable/data.table

- R package 다운로드, 매뉴얼, 튜토리얼: https://cran.r-project.org/web/packages/data.table/ 

 

 

많은 도움이 되었기를 바랍니다. 

다음번 포스팅에서는 R data.table 의 데이터셋을 만드는 3가지 방법을 소개하겠습니다. 

 

 

 

  (5) R data.table 구문 소개 포스팅 링크

 

 

  • 패턴이 있는 여러개의 칼럼을 동시에 길게 녹이고(melt), 옆으로 넓게 주조(cast) 하기
    :
    rfriend.tistory.com/576

  • 그룹별 관측치 개수 별로 Data.Table을 구분해서 생성하기
    :
    rfriend.tistory.com/607

 

 

  • 회귀 모델의 오른쪽 부분(model's right-hand side)의 변수 조합을 일괄 다루기
    : rfriend.tistory.com/609

 

  • R data.table의 조건이 있는 상태에서 Key를 기준으로 데이터셋 합치기(Conditional Joins)
    :
    rfriend.tistory.com/610

 

  • R data.table의 .SD[], by 를 사용해서 그룹별로 부분집합 가져오기 (Group Subsetting)

    : rfriend.tistory.com/611

 

  • R data.table 그룹별 선형회귀모형 적합하고 회귀계수 구하기 (Grouped Regression)
    :
    rfriend.tistory.com/614

 

  • R data.table 이차 인덱스(Secondary indices)를 활용한 빠른 탐색 기반 subsetting
    : rfriend.tistory.com/615

 

 

 

이번 포스팅이 도움이 되었다면 아래의 '공감~

'를 꾹 눌러주세요.

행복한 데이터 과학자 되세요. :-)

 

 

728x90
반응형
Posted by Rfriend
,

자동미분(Automatic differentiation, Algorithmic differentiation, computational differentiation, auto-differentiation, 또는 줄여서 간단히 auto-diff) 은 컴퓨터 프로그램에 의해서 구체화된 함수의 미분을 산술적으로 계산할 때 사용하는 기술의 집합을 말합니다. 컴퓨터 프로그램에서 구체화한 함수는 아무리 복잡해보이더라도 기본적인 산술 연산 (덧셈, 뺄셈, 곱셉, 나눗셈 등)과 기본적인 함수 (지수, 로그, 싸인, 코싸인 등) 의 연속적인 실행으로 이루어진다는 아이디어를 기반으로 합니다. 복잡한 함수도 연쇄 법칙(Chain Rule)을 이용함으로써 합성함수를 구성하는 각 기본함의 미분의 곱으로 나타내고 반복적으로 계산함으로써 자동으로 복잡한 함수를 정확하고 효율적으로 미분할 수 있습니다.   


자동미분(Automatic Differentiation)은 딥러닝에서 오차 역전파 알고리즘을 사용해서 모델을 학습할 때 유용하게 사용합니다. TensorFlow는 Gradient Tapes 를 이용하면 즉시 실행 모드(eager execution mode)에서 쉽게 오차 역전파를 수행할 수 있습니다. 


이번 포스팅에서는 


1. 즉시실행모드에서 자동미분을 하기 위해 Gradient tapes 이 필요한 이유

2. TensorFlow에서 Gradient tapes를 이용해 자동미분하는 방법

3. 시그모이드 함수 자동미분 시각화

4. 테이프가 볼 것을 조정하는 방법 (Controlling what the tape watches)

5. Python 조건절 분기문을 이용한 테이프 기록 흐름 조정 (Control flow)


에 대해서 소개하겠습니다. 


이번 포스팅은 TensorFlow 튜토리얼의 내용을 번역하고 코드를 대부분 사용하였습니다. 


먼저 numpy, tensorflow, matplotlib 모듈을 importing 하겠습니다. 



import numpy as np

import matplotlib.pyplot as plt

import tensorflow as tf


print(tf.__version__)

2.4.0-dev20200913




  1. 즉시 실행 모드에서 자동미분을 하기 위해 Gradient tapes 이 필요한 이유


예를 들어서  y = a * x 라는 방정식에서 a 와 y 가 상수(constant)이고 x 가 변수(Varialbe) 일 때, 이 방정식을 오차역전파법으로 풀어서 변수 x 를 구하고자 한다고 합시다. 그러면 간단한 손실함수인 loss = abs(a * x - y) 를 최소로 하는 x 를 구하면 됩니다. 


아래 예의 경우 8.0 = 2.0 * x 방정식 함수로 부터 변수 x 의 값을 구하고자 합니다. x 를 10.0 에서 시작해서 abs(a * x - y) 손실함수 값을 최소로 하는 x 를 구하려면 '손실함수에 대한 x의 미분 (the gradient of the loss with respect to x)를 구해서 x 값을 미분값만큼 빼서 갱신해주어야 합니다. 


그런데 아래의 TensorFlow 2.x 버전에서의 Python 즉시실행모드(eager mode)에서 손실(Loss) 을 "바로 즉시 계산"(eager execution)해보려서 출력 결과를 보면 numpy=12.0 인 Tensor 상수입니다. 여기서 자동미분을 하려고 하면 문제가 생기는데요, 왜냐하면 자동미분을 하기 위해 필요한 함수와 계산 식의 연산 과정과 입력 값에 대한 정보가 즉시실행모드에서는 없기 때문입니다. (입력 값과 연산 과정은 저장 안하고 즉시실행해서 결과만 출력했음).  



a, y = tf.constant(2.0), tf.constant(8.0)

x = tf.Variable(10.0) # In practice, we start with a random value

loss = tf.math.abs(a * x - y)


loss

[Out] <tf.Tensor: shape=(), dtype=float32, numpy=12.0>

 




  2. TensorFlow에서 Gradient tapes를 이용해 자동미분하는 방법


이 문제를 해결하기 위해 TensorFlow 는 중간 연산 과정(함수, 연산)을 테이프(tape)에 차곡차곡 기록해주는 Gradient tapes 를 제공합니다. 


with tf.GradientTape() as tape: 로 저장할 tape을 지정해주면, 이후의 GradientTape() 문맥 아래에서의 TensorFlow의 연관 연산 코드는 tape에 저장이 됩니다. 이렇게 tape에 저장된 연산 과정 (함수, 연산식) 을 가져다가 TensorFlow는 dx = tape.gradient(loss, x) 로 후진 모드 자동 미분 (Reverse mode automatic differentiation) 방법으로 손실에 대한 x의 미분을 계산합니다. 이렇게 계산한 손실에 대한 x의 미분을 역전파(backpropagation)하여 x의 값을 갱신(update)하는 작업을 반복하므로써 변수 x의 답을 찾아가는 학습을 진행합니다. 


위의 (1)번에서 소개한 8.0 = 2.0 * x 의 방정식에서는 변수 x = 4.0 이 답인데요, TensorFlow 의 GradientTape() 과 tape.gradient() 를 사용해서 오차 역전파 방법으로 갱신하여 문제를 풀어보겠습니다. 처음에 x = 10.0에서 시작형 4회 반복하니 x = 4.0 으로 답을 잘 찾아갔군요.  



# UDF for training

def train_func():

    with tf.GradientTape() as tape:

        loss = tf.math.abs(a * x - y)

    

    # calculate gradient

    dx =  tape.gradient(loss, x)

    print('x = {}, dx = {:.2f}'.format(x.numpy(), dx))

    

    # update x <- x - dx

    x.assign(x - dx)



# Run train_func() UDF repeately

for i in range(4):

    train_func()


[Out]
x = 10.0, dx = 2.00
x = 8.0, dx = 2.00
x = 6.0, dx = 2.00
x = 4.0, dx = 0.00

 




라는 함수에서 (Target) y에 대한 (Source) x의 미분 (derivative of target y with respect to source x) 을 TensorFlow의 GradientTape.gradient() 메소드를 사용해서 계산해보겠습니다. (우리는 이미 고등학교 때 배웠던 미분에 따라 라는 것을 알고 있습니다. x = 4.0 이므로 dy/dx 를 풀면 2 * 4.0 = 8.0 이겠군요.)


GradientTape.gradient(target, sources)



x = tf.Variable(4.0)


with tf.GradientTape() as tape:

    y = x ** 2

    

    

# dy = 2x * dx

dy_dx = tape.gradient(y, x)

dy_dx.numpy()

[Out] 8.0

 




위의 간단한 예에서는 스칼라(Scalar) 를 사용하였다면, tf.GradientTape() 은 어떤 형태의 텐서에 대해서도 사용할 수 있습니다. GradientTape.gradient(target, sources) 에서 sources 에는 여러개 변수를 리스트나 사전형(Dictionary) 형태로 입력해줄 수 있습니다.  



# tf.GradientTape works as easily on any tensor. 

w = tf.Variable(tf.random.normal((4, 2)), name='w')

b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b')

x = [[1.0, 2.0, 3.0, 4.0]]


with tf.GradientTape(persistent=True) as tape:

    y = x @ w + b

    loss = tf.reduce_mean(y ** 2)

    

# To get the gradienet of y w.r.t both variables, w and b

[dl_dw, dl_db] = tape.gradient(loss, [w, b])



print('loss:', loss)

print('w:', w)

print('b:', b)

[Out]
loss: tf.Tensor(74.95752, shape=(), dtype=float32)
w: <tf.Variable 'w:0' shape=(4, 2) dtype=float32, numpy=
array([[-0.05742593, -0.06279723],
       [-0.7892129 ,  1.8175325 ],
       [ 3.1122675 ,  0.12920259],
       [ 0.8164586 ,  0.3712036 ]], dtype=float32)>
b: <tf.Variable 'b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)>

 



Target에 대한 Source의 미분 결과는 Source의 형상(shape)을 따릅니다. 가령, 위의 예에서 손실함수(Loss)에 대한 가중치(w)의 미분 (derivative of loss with respect to weight) 의 결과는 Source에 해당하는 가중치(w)의 형상을 따라서 (4, 2) 가 됩니다. 



print('shape of w:', w.shape)

print('shape of dl_dw:', dl_dw.shape)

[Out]
shape of w: (4, 2)
shape of dl_dw: (4, 2)

 




  3. 시그모이드 함수의 자동미분 시각화 (Auto-diff plot of Sigmoid function)


딥러닝에서 활성화 함수 중의 하나로 사용되는 S 곡선 모양의 시그모이드 함수 (Sigmoid function) 은 아래와 같습니다. 


이 시그모이드 함수를 x 에 대해서 미분하면 아래와 같습니다. 


시그모이드 함수와 시그모이드 함수의 미분 값을 x 가 -10.0에서 10.0 사이의 200개의 관측치에 대해 TensorFlow의 tf.GradientTape() 와 GradientTae=pe.gradient(Sigmoid_func, x) 로 계산해서 하나의 그래프에 겹쳐서 시각화를 해보겠습니다. 



x = tf.linspace(-10.0, 10.0, 200+1)


with tf.GradientTape() as tape:

    tape.watch(x)

    y = tf.nn.sigmoid(x)


# For an element-wise calculation, 

# the gradient of the sum gives the derivative of each element

# with respect to its input-element, since each element is independent.

dy_dx = tape.gradient(y, x)



# plot of y, dy/dx

plt.plot(x, y, 'r-', label='y')

plt.plot(x, dy_dx, 'b--', label='dy/dx')

plt.legend()

_ = plt.xlabel('x')


plt.show()






  4. 테이프가 "볼(저장)" 것을 조정하는 방법 (Colltrolling whtat the tape "watches")


TensorFlow 가 오차역전파 알고리즘으로 학습(training by backpropagation algorithm)할 때 사용하는 자동미분은 '학습 가능한 변수 (trainable tf.Variable)'를 대상으로 합니다. 


따라서 만약에 (1) 변수가 아닌 상수 ('tf.Tensor') 라면 TensorFlow가 안 보는 (not watched), 즉 기록하지 않는 (not recorded) 것이 기본 설정이므로 자동 미분을 할 수 없으며, (2) 변수(tf.Variable) 더라도 '학습 가능하지 않으면 (not trainable)' 자동 미분을 할 수 없습니다



# A trainable variable

x0 = tf.Variable(3.0, name='x0')


# Not trainable

x1 = tf.Variable(3.0, name='x1', trainable=False)


# Not a Variable: A variable + tensor returns a tensor.

x2 = tf.Variable(2.0, name='x2') + 1.0


# Not a variable

x3 = tf.constant(3.0, name='x3')


with tf.GradientTape() as tape:

    y = (x0**2) + (x1**2) + (x2**2)


# Only x0 is a trainable tf.Variable, --> can calculate gradient

grad = tape.gradient(y, [x0, x1, x2, x3]) 



for g in grad:

    print(g)


[Out] 

tf.Tensor(6.0, shape=(), dtype=float32) # <-- dy_dx0, trainable variable None # <-- dy_dx1, not trainable None # <-- dy_dx2, not a variable None # <-- dy_dx3, not a variable

 



y 에 대한 tf.Tensor 인 x의 미분을 구하기 위해서는 GradientTape.watch(x) 를 호출해서 tf.Tensor 인 x를 테이프에 기록해주면 됩니다. 



x = tf.constant(3.0)

with tf.GradientTape() as tape:

    tape.watch(x) # watch 'x' and then record it onto tape

    y = x**2


# dy = 2x * dx

dy_dx = tape.gradient(y, x)

print(dy_dx.numpy())

[Out] 6.0

 



TensorFlow의 테이프에 기록되는 '학습 가능한 변수'는 GradientTape.watched_variables() 메소드로 확인할 수 있습니다. 



[var.name for var in tape.watched_variables()]

[Out] ['Variable:0']

 



반대로, 모든 변수(tf.Variable)를 테이프에 기록하는 TensorFlow의 기본설정을 비활성화(disable)하려면 watch_accessed_variables=False 으로 매개변수를 설정해주면 됩니다. 

아래의 예에서는 x0, x1 의 두 개의 tf.Variable 모두를 watch_accessed_variables=False 로 비활성화해놓고, GradientTape.watch(x1) 으로 x1 변수만 테이프에 기록해서 자동 미분을 해보겠습니다. 



x0 = tf.Variable(0.0)

x1 = tf.Variable(10.0)


with tf.GradientTape(watch_accessed_variables=False) as tape:

    tape.watch(x1)

    y0 = tf.math.sin(x0)

    y1 = tf.nn.softplus(x1)

    y = y0 + y1

    ys = tf.reduce_sum(y)



# dy = 2x * dx

grad = tape.gradient(ys, {'x0': x0, 'x1': x1})


print('dy/dx0:', grad['x0'])

print('dy/dx1:', grad['x1'].numpy())

[Out]

dy/dx0: None dy/dx1: 0.9999546

 




  5. Python 조건절을 사용한 테이프 기록 흐름을 조정

     (Control flow using Python condition statements)


Python 의 조건절을 이용한 분기문을 이용하면 TensorFlow 의 tf.GradientTape() 맥락 아래에서 테이프에 기록하는 연산 과정의 흐름을 조건절에 해당하는 것만 취사선택해서 기록할 수 있습니다. 


아래 예에서는 if x > 0.0 일 때는 result = v0, 그렇지 않을 때는(else) result = v1 ** 2 로 result 의 연산을 다르게 해서 테이프에 기록하고, GradientTape.gradient() 로 자동미분 할 때는 앞서 if else 조건절에서 True에 해당되어서 실제 연산이 일어났던 연산에 미분을 연결해서 계산하라는 코드입니다. 


아래 예에서는 x = tf.constant(1.0) 으로서 if x > 0.0 조건절을 만족(True)하므로 result = v0 연산이 실행되고 뒤에 tape.gradient()에서 자동미분이 되었으며, else 조건절 아래의 연산은 실행되지 않았으므로 뒤에 tape.gradient()의 자동미분은 해당되지 않았습니다 (dv1: None)



x = tf.constant(1.0)


v0 = tf.Variable(2.0)

v1 = tf.Variable(2.0)


with tf.GradientTape(persistent=True) as tape:

    tape.watch(x)


    # Python flow control

    if x > 0.0:

        result = v0

    else:

        result = v1**2 


dv0, dv1 = tape.gradient(result, [v0, v1])


print('dv0:', dv0)

print('dv1:', dv1)

[Out] 
dv0: tf.Tensor(1.0, shape=(), dtype=float32)
dv1: None

 



[ Reference ]

* Automatic Differentiation: https://en.wikipedia.org/wiki/Automatic_differentiation

* TensorFlow Gradient tapes and Automatic differentiation
   : https://www.tensorflow.org/guide/autodiff?hl=en


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

TensorFlow에서 그래프(Graphs)는 tf.Operation 객체의 집합을 포함하고 있는 데이터 구조로서, 연산의 단위와 텐서 객체, 연산간에 흐르는 데이터 단위를 나타냅니다. ("Graphs are data structures that contain a set of tf.Operation objects, which represent units of computation; and tf.Tensor objects, which represent the units of data that flow between operations.") 

* source: https://www.tensorflow.org/guide/intro_to_graphs


TensorFlow 1.x 버전에서의 지연 실행(Lazy execution) 모드 에서는 연산 그래프 (Computational Graph)를 생성하고, 연산을 수행하기 이전에 그래프로 부터 텐서를 어떤 연산을 어떤 순서로 최적화해서 수행할지를 결정한 다음에야 연산을 게으르게(Lazy) 수행을 합니다. TensorFlow 1.x 버전에서는 Session 을 열어서 그래프를 명시적으로 작동(explicitly run)시켜줘야 합니다. 


그래프 최적화(Graph Optimization) 는 만약 동일한 연산이 여기저기서 반복되는 경우 해당 연산 결과를 캐쉬(cache)로 저장해서 사용함으로써 동일 연산이 반복적으로 일어나지 않도록 한다거나, 복잡한 연산의 경우 다수의 장비에서 병렬처리(parallel on multiple devices)를 하여 연산을 빠르게 수행할 수 있도록 하여 성능을 최적화해줍니다. 


TensorFlow 2.x 버전부터는 즉시 실행(Eager execution) 모드로 바뀌었다고 지난 포스팅에서 소개하였는데요, 이는 코드가 즉시 수행된다는 뜻으로서, 즉 "연산 그래프를 그리고 그래프 최적화를 한 후에 코드를 지연 수행"하는 것이 아니라는 뜻입니다. 


그런데 만약 복잡한 모델의 성능을 최적화할 필요가 있다거나, 다른 장비로 모델을 내보내야한다거나 할 때(가령, Python interpreter가 없는 모바일 애플리케이션, 임베디드 장비, 백엔드 서버 등), 파이썬의 즉시 실행 코드를 TensorFlow Graph 로 변환해야 할 필요가 생깁니다. 


이때 TensorFlow 2.x 에서 사용할 수 있는 것이 바로 AutoGraph 모듈과 tf.function 데코레이터(tf.function Python decorator) 입니다. TensorFlow 1.x 버전때처럼 Placeholder를 정의하거나 Session을 열고 작동시켜야하는 번거로움이 TensorFlow 2.x 에서는 없으므로 무척 편리합니다. 




TensorFlow 2.3.0 버전을 사용해서 간단한 예를 들어보겠습니다. 



import tensorflow as tf


print(tf.__version__)

[Out] 2.3.0

 



a, b, c 세개의 객체를 input으로 받아서 곱셈과 덧셈 연산을 해서 얻은 d, e 객체를 반환하는 간단한 Python 사용자 정의 함수 (User defined function) 를 만들어서 실행시켜 보겠습니다.  



# python UDF

def calc(a, b, c):

    d = a * b + c

    e = a * b * c

    return d, e

 

# run python UDF

d, e = calc(2, 3, 4)


print('d:', d)

print('e:', e)

[Out]

d: 10 e: 24




위에서 만든 Python code 를 TensorFlow 2.x 버전에서 텐서를 input으로 받아서 그래프 최적화 (Graph Optimization) 하여 연산을 수행할 수 있도록 @tf.function 과 AutoGraph 모듈을 사용하여 자동으로 그래프로 변환하여 보겠습니다. Python code 함수를 정의하기 전에 @tf.function 데코레이터를 써주면 됩니다. 



@tf.function

def calc(a, b, c):

    d = a * b + c

    e = a * b * c

    return d, e

 

 


혹시 Python 함수가 AutoGraph 에서 그래프로 어떻게 변환되었는지 궁금하다면 아래와 같이 tf.autograph.to_code() 함수를 사용해서 확인해볼 수 있습니다. (좀 복잡하군요. ^^; 이걸 알아서 자동으로 해주니 참 좋은 세상입니다! Many thanks to TensorFlow and Google!)



# If you're curious you can inspect the code autograph generates.

print(tf.autograph.to_code(calc.python_function))


def tf__calc(a, b, c):
    with ag__.FunctionScope('calc', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()
        d = ((ag__.ld(a) * ag__.ld(b)) + ag__.ld(c))
        e = ((ag__.ld(a) * ag__.ld(b)) * ag__.ld(c))
        try:
            do_return = True
            retval_ = (ag__.ld(d), ag__.ld(e))
        except:
            do_return = False
            raise
        return fscope.ret(retval_, do_return)

 




TensorFlow 1.x 버전에서는 Session을 열고 명시적으로 그래프 연산을 동작을 시켜야 하는 반면에, TensorFlow 2.x 버전에서는 Session 없이 바로 그래프 연산을 수행할 수 있습니다. 


Session 없이 바로 위에서 정의하고 그래프로 변환했던 calc() 함수에 a, b, c 상수 텐서를 입력해서 d, e 텐서를 결과로 반환해보겠습니다. 



# input constant tensor

a = tf.constant(2.0, name="a")

b = tf.constant(3.0, name="b")

c = tf.constant(4.0, name="c")


# run AutoGraph function without opening Session()

d, e = calc(a, b, c)


print('d:', d)

print('e:', e)

[Out] 
d: tf.Tensor(10.0, shape=(), dtype=float32)
e: tf.Tensor(24.0, shape=(), dtype=float32)


# convert tensor into numpy array format

(d.numpy(), e.numpy())

[Out] (10.0, 24.0)

 


[ Reference ] 

* Better performance with tf.function: https://www.tensorflow.org/guide/function

TensorFlow 2.0: tf.function and AutoGraph: https://towardsdatascience.com/tensorflow-2-0-tf-function-and-autograph-af2b974cf4f7


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 TensorFlow 2.x 버전의 Keras를 사용하여 MNIST 손글씨를 분류하는 간단한 DNN (Deep Neural Network) 모델을 만드는 방법(DNN (https://rfriend.tistory.com/553)을 소개하였습니다. 


이번 포스팅에서는 TensorFlow 에서 모델을 개발하는 과정과 결과를 시각화하여 모니터링할 수 있도록 해주는 웹 기반의 시각화 툴인 TensorBoard 사용 방법을 소개하겠습니다. 


아래 이미지는 https://keras.io 페이지의 Keras 소개 페이지에 나오는 내용인데요, Keras로 쉽고 빠르게 딥러닝 모델을 만들어서 실험하고, 여러개의 모델 실험 결과를 TensorBoard 를 사용해서 시각화하여 확인하면서 "생각의 속도로 반복(Iterate at the speed of though)" 하라고 하네요! 



TensorBoard 를 사용하면 다수의 모델 각 각 별로 쉽고 빠르게 


  • TensorFlow, Keras 로 만든 모델 구조의 그래프(Graphs)를 시각화

     --> 모델의 그래프가 의도한 설계와 일치하는지 확인

     --> op-level 그래프를 통해 TensorFlow가 프로그램을 어떻게 이해하고 있는지 확인

  • TensorFlow, Keras 로 만든 모델의 학습 과정/ 결과 (Epoch 별 정확도, 손실값)를 그래프로 시각화
  • 활성화와 경사를 히스토그램으로 시각화
  • 3D로 Embedding 탐색


을 할 수 있습니다. 



TensorBoard 사용 예를 들기 위하여 

(1) Keras로 Fashion MNIST 데이터를 분류하는 Dense 모델 구축

(2) Keras TensorBoard callback 을 포함하여 모델 학습 (Train the model with TensorBoard callback)

(3) TensorBoard 를 실행하여 모델 학습 과정/결과 시각화


의 순서대로 소개를 하겠습니다. 



  (1) Keras로 Fashion MNIST 데이터를 분류하는 Dense 모델 구축


먼저 TensorFlow와 TensorBoard 를 importing 하고 버전을 확인해보겠습니다. TensorFlow 2.3.0 버전이므로 Keras를 따로 importing 할 필요는 없습니다. 



import tensorflow as tf

tf.__version__

[Out] '2.3.0'

 

import tensorboard

tensorboard.__version__

[Out] '2.3.0'




이번 포스팅의 목적은 TensorBoard 사용법을 소개하는 것이므로 Fashion MNIST의 Training set 만을 로딩해보겠습니다. (Test set 은 사용 안 함.)

 


(train_images, train_labels), _ = tf.keras.datasets.fashion_mnist.load_data()

train_images = train_images / 255.0 # normailization (0 ~ 1)


print('shape of train images:', train_images.shape)

print('shape of train labels:', train_labels.shape)

[Out]
shape of train images: (60000, 28, 28)
shape of train labels: (60000,)

 



모델 코드는 https://www.tensorflow.org/tensorboard/graphs 페이지에서 소개하는 DNN 매우 간단한 Keras 코드를 그대로 사용하였습니다. 1개의 Dense 은닉층, Dropout 층, 그리고 10개의 classes별 확률을 반환하는 Output 층을 순서대로 쌓아서(sequential stack of layers) 딥러닝 모델을 구축하였습니다. 


그리고 최적화 알고리즘, 손실함수, 평가지표를 설정해서 모델을 컴파일 해주었습니다. 



# Define the model

model = tf.keras.models.Sequential([

    tf.keras.layers.Flatten(input_shape=(28, 28)),

    tf.keras.layers.Dense(32, activation='relu'),

    tf.keras.layers.Dropout(0.2),

    tf.keras.layers.Dense(10, activation='softmax')

])


# Compile the model

model.compile(

    optimizer='adam',

    loss='sparse_categorical_crossentropy',

    metrics=['accuracy'])




여기까지는 지난번 포스팅에서 소개했던 Keras로 모델 구축하고 컴파일하는 방법과 동일하구요, 이제 (2)번 부터 TensorBoard를 이용하기 위해서 추가로 수고를 해줘야 하는 부분입니다. 



  (2) Keras TensorBoard callback을 포함하여 모델 학습 

      (Train the model with Keras TensorBoard callback)


(2-1) 먼저, Keras TensorBoard callback 을 정의해줍니다. 

"./logs/"의 하위경로에 모델을 훈련시키는 시점의 "년월일-시간분초(%Y%m%d-%H%M%S)" 를 이름으로 가지는 하위 폴더를 추가하여 Log를 저장해줄 경로(log_dir)를 지정해주겠습니다. (모델을 여러개 구축하여 실험할 경우 버전 구분/ 관리 용도)



# Define the Keras TensorBoard callback.

from datetime import datetime


logdir="logs/" + datetime.now().strftime("%Y%m%d-%H%M%S")

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=logdir)




현재 작업하는 경로는 os 라이브러리의 os.getcwd() 메소드로 확인할 수 있습니다. 저의 경우는 Keras TensorBoard callback 의 로그 저장 경로가 "/Users/ihongdon/Documents/TensorFlow/logs/20200822-181251" 가 되겠군요. 



import os

os.getcwd()

[Out] '/Users/ihongdon/Documents/TensorFlow'


logdir

[Out] 'logs/20200822-181251'

 



(2-2) 다음으로, 위의 (2-1)에서 정의한 Keras TensorBoard callback 을 지정(아래의 빨간색 부분)하여 모델을 훈련(training, fitting) 시킵니다. 



# Train the model.

model.fit(

    train_images,

    train_labels, 

    batch_size=64,

    epochs=5, 

    callbacks=[tensorboard_callback])


Epoch 1/5
  1/938 [..............................] - ETA: 0s - loss: 2.3300 - accuracy: 0.0781WARNING:tensorflow:From /Users/ihongdon/anaconda3/envs/py3.8_tf2.3/lib/python3.8/site-packages/tensorflow/python/ops/summary_ops_v2.py:1277: stop (from tensorflow.python.eager.profiler) is deprecated and will be removed after 2020-07-01.
Instructions for updating:
use `tf.profiler.experimental.stop` instead.
938/938 [==============================] - 1s 690us/step - loss: 0.6749 - accuracy: 0.7662
Epoch 2/5
938/938 [==============================] - 1s 698us/step - loss: 0.4813 - accuracy: 0.8287
Epoch 3/5
938/938 [==============================] - 1s 796us/step - loss: 0.4399 - accuracy: 0.8425
Epoch 4/5
938/938 [==============================] - 1s 766us/step - loss: 0.4184 - accuracy: 0.8493
Epoch 5/5
938/938 [==============================] - 1s 684us/step - loss: 0.4044 - accuracy: 0.8543
Out[6]:
<tensorflow.python.keras.callbacks.History at 0x7fca47797ca0>

 




  (3) TensorBoard 를 실행하여 모델 학습 과정/결과 시각화 (TensorFlow Visualization)


이제 TensorBoard 를 실행시켜서 위의 (2)번에서 학습하고 있는 (혹은 학습이 끝난) 모델을 시각화하여 눈으로 직접 그래프와 손실값/정확도를 확인을 해보겠습니다. 


(3-1) 명령 프롬프트(commamd prompt) 창을 열어주세요.

(위의 (1)번과 (2)번은 Jupyter Notebook에서 실행, 여기 (3)번은 명령 프롬프트 창에서 실행)


(3-2) 그리고 Anaconda 가상환경들 중에서 하나를 선택해서 활성화시켜주세요. 


 * Mac OS 의 경우       --> % source activate [virtual_env_name]

 * Windows OS 의 경우 --> % activate [virtual_env_name]

 



(3-3) 명령 프롬프트 창에서 가상환경에 들어왔으면, 앞의 (2-1)에서 정의했던 TensorBoard 의 로그 경로를 지정해서 TensorBoard를 실행시킵니다. 


% tensorboard --logdir= [TensorBoard Log 저장 경로]


아래의 명령 프롬프트 화면캡쳐한 이미지의 제일 마지막 줄을 보면 "http://localhost:6006/" 의 웹 주소에서 TensorBoard 를 볼 수 있다고 안내가 나옵니다. (Localhost의 6006번 포트) 


만약 6006 포트 말고 다른 포트를 사용하고 싶으면 --port=[port 번호] 식으로 명령 프롬프트 창에서 옵션 명령어를 추가해주면 됩니다. 


% tensorboard --logdir= [TensorBoard Log 저장 경로] --port=9000





TensorBoard 를 종료하려면 명령 프롬프트 창에서 'CTRL+C' 을 눌러주면 됩니다. 



*********************************************************************************************************

 

(3-4) 이제  Chrome이나 Firefox 같은 웹 브라우저를 열고 "http://localhost:6006/" url로 TensorBoard 에 접속해보세요. 


짜잔~ TensorBoard 가 떴습니다~! 이예~!!! ^_____^


"SCALARS"와 "GRAPHS"의 두 개의 메뉴가 있는데요, 먼저 "SCARLARS" 메뉴에 들어가서 왼쪽 하단의 모델 이름 ("20200822-181251/train") 을 선택해주면 각 epoch별로 정확도(accuracy)와 손실값(loss value, 이 예에서는 categorical cross-entropy)을 선 그래프로 해서 시각화하여 볼 수 있습니다. 

(이 예는 데이터도 작고, 5 epochs 밖에 안되다 보니 벌써 다 끝나있네요.)




다음으로 TensorBoard의 상단에 있는 "GRAPHS" 메뉴를 클릭해서 (1)번에서 구축한 Dense 모델의 그래프를 확인해보겠습니다. 





그래프 노드의 '+' 를 클릭하면 해당 층(Layer)의 세부 그래프를 확대(Zoom In)하여 살펴볼 수 있습니다. 


가령, 아래의 TensorBoard 화면캡쳐처럼 위의 상위 수준의 그래프에서  'dense' 노드의 '+'를 클릭하여 세부적인 연산 그래프를 확인해보겠습니다. 1차원으로 flatten 된 input 데이터와 weight 간의 행렬곱(MatMul) 후에, 편향을 더해주고(BiasAdd), ReLU 활성화함수를 적용하는 순서로 TensorFlow가 이해를 하고 있군요. 


펼쳐진 세부 그래프 블럭을 더블 클릭해주면 다시 원래의 노드로 Zoom Out 됩니다. 




* Reference: https://www.tensorflow.org/tensorboard/graphs


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 TensorFlow 1.x 버전이 지연(게으른) 실행 (Lazy Execution) 인데 반해 TensorFlow 2.x 버전은 즉시 실행 (Eager Execution) 으로 바뀌었다는 점에 대해서 TenforFlow의 대표적인 자료구조인 상수, 변수, Placeholder 별로 간단한 예를 들어서 비교 설명해 보았습니다. 


이번 포스팅에서는 TensorFlow 2.x 버전에서 크게 바뀐 것 중에서 "고수준 API (High-Level API) 로서 Keras API를 채택"하였다는 것에 대해서 소개하겠습니다. 


TensorFlow 2.x 는 텐서 연산은 C++로 고속으로 수행하며, 저수준 API에는 Python wrapper 의 TensorFlow가 있고, 고수준 API로는 Keras 를 사용하는 아키텍처입니다. Keras도 당연히 CPU 뿐만 아니라 GPU 를 사용한 병렬처리 학습이 가능하며, 다양한 애플리케이션/디바이스에 배포도 가능합니다. 



Keras는 Google 에서 딥러닝 연구를 하고 있는 Francois Chollet 이 만든 Deep Learning Library 인데요, TensorFlow 2.x 버전에 고수준 API 로 포함이 되었습니다. (고마워요 Google!!!)


TensorFlow 1.x 버전을 사용할 때는 TensorFlow 설치와 별도로 Native Keras를 따로 설치하고 또 Keras를 별도로 import 해서 사용해야 했습니다. 


하지만 TensorFlow 2.x 버전 부터는 TensorFlow 만 설치하면 되며, Keras는 별도로 설치하지 않아도 됩니다. 또한 Keras API를 사용할 때도 TensorFlow를 Importing 한 후에 tf.keras.models 와 같이 tf.keras 로 시작해서 뒤에 Keras의 메소드를 써주면 됩니다. (아래의 MNIST 분류 예시에서 자세히 소개)


 

TensorFlow 1.x 

TensorFlow 2.x 

 설치 (Install)

 $ pip install tensorflow==1.15

 $ pip install keras

 $ pip install tensorflow==2.3.0 

 (Keras는 별도 설치 필요 없음)

 Importing

 import tensorflow as tf

from keras import layers

 import tensorflow as tf

 from tf.keras import layers




위의 Kreas 로고 이미지는 https://keras.io 정식 홈페이지의 메인 화면에 있는 것인데요, "Simple. Flexible. Powerful." 가 Keras의 모토이며, Keras를 아래처럼 소개하고 있습니다. 


"Keras는 TensorFlow 기계학습 플랫폼 위에서 실행되는 Python 기반의 딥러닝 API 입니다. Keras는 빠른 실험을 가능하게 해주는데 초점을 두고 개발되었습니다. 아이디어에서부터 결과를 얻기까지 가능한 빠르게 할 수 있다는 것은 훌륭한 연구를 하는데 있어 매우 중요합니다" 

* Keras reference: https://keras.io/about/


백문이 불여일견이라고, Keras를 사용하여 MNIST 손글씨 0~9 숫자를 분류하는 매우 간단한 DNN (Deep Neural Network) 모델을 만들어보겠습니다.


DNN Classifier 모델 개발 및 적용 예시는 (1) 데이터 준비, (2) 모델 구축, (3) 모델 컴파일, (4) 모델 학습, (5) 모델 평가, (6) 예측의 절차에 따라서 진행하겠습니다. 




[ MNIST 데이터 손글씨 숫자 분류 DNN 모델 ]



  (1) 데이터 준비 (Preparing the data)


먼저 TensorFlow 를 importing 하고 버전(TensorFlow 2.3.0)을 확인해보겠습니다. 

 


import tensorflow as tf

import numpy as np


print(tf.__version__)

2.3.0




Keras 에는 MNIST 데이터셋이 내장되어 있어서 tf.keras.datasets.mnist.load_data() 메소드로 로딩을 할 수 있습니다. Training set에 6만개, Test set에 1만개의 손글씨 숫자 데이터와 정답지 라벨 데이터가 있습니다. 



(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()


print('shape of x_train:', x_train.shape)

print('shape of y_train:', y_train.shape)

print('shape of x_test:', x_test.shape)

print('shape of y_test:', y_test.shape)

shape of x_train: (60000, 28, 28)
shape of y_train: (60000,)
shape of x_test: (10000, 28, 28)
shape of y_test: (10000,)




 MNIST 데이터셋이 어떻게 생겼는지 확인해보기 위해서 Training set의 0번째 이미지 데이터 배열(28 by 28 array)과 정답지 라벨 ('5'), 그리고 matplotlib 으로 시각화를 해보겠습니다. 



x_train[0]

[Out] array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 18, 18, 18, 126, 136, 175, 26, 166, 255, 247, 127, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 30, 36, 94, 154, 170, 253, 253, 253, 253, 253, 225, 172, 253, 242, 195, 64, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 49, 238, 253, 253, 253, 253, 253, 253, 253, 253, 251, 93, 82, 82, 56, 39, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 18, 219, 253, 253, 253, 253, 253, 198, 182, 247, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 80, 156, 107, 253, 253, 205, 11, 0, 43, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 1, 154, 253, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 139, 253, 190, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 190, 253, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 241, 225, 160, 108, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 240, 253, 253, 119, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 186, 253, 253, 150, 27, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 93, 252, 253, 187, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 249, 253, 249, 64, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 130, 183, 253, 253, 207, 2, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 148, 229, 253, 253, 253, 250, 182, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 114, 221, 253, 253, 253, 253, 201, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 23, 66, 213, 253, 253, 253, 253, 198, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 18, 171, 219, 253, 253, 253, 253, 195, 80, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 55, 172, 226, 253, 253, 253, 253, 244, 133, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 136, 253, 253, 253, 212, 135, 132, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)



y_train[0]

[Out] 5


import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (5, 5)

plt.imshow(x_train[0])

plt.show()




Deep Neural Network 모델의 input으로 넣기 위해 (28 by 28) 2차원 배열 (2D array) 이미지를 (28 * 28 = 784) 의 옆으로 길게 펼친 데이터 형태로 변형(Reshape) 하겠습니다(Flatten() 메소드를 사용해도 됨). 그리고 0~255 사이의 값을 0~1 사이의 실수 값으로 변환하겠습니다.  



# reshape and normalization

x_train = x_train.reshape((60000, 28 * 28)) / 255.0

x_test = x_test.reshape((10000, 28 * 28)) / 255.0





  (2) 모델 구축 (Building the model)


이제 본격적으로 Keras를 사용해보겠습니다. Keras의 핵심 데이터 구조로 모델(Models)층(Layers)있습니다. 모델을 구축할 때는 (a) 순차적으로 층을 쌓아가는 Sequential model 과, (b) 복잡한 구조의 모델을 만들 때 사용하는 Keras functional API 의 두 가지 방법이 있습니다. 


이미지 데이터 분류는 CNN (Convolutional Neural Network) 을 사용하면 효과적인데요, 이번 포스팅에서는 간단하게 Sequential model 을 사용하여 위의 (1)에서 전처리한 Input 이미지 데이터를 받아서, 1개의 완전히 연결된 은닉층 (Fully Connected Hidden Layer) 을 쌓고, 10개의 classes 별 확률을 반환하는 FC(Fully Connected) Output Layer 를 쌓아서 만든 DNN(Deep Neural Network) 모델을 만들어보겠습니다.  (Functional API 는 PyTorch 와 유사한데요, 나중에 별도의 포스팅에서 소개하도록 하겠습니다.)


add() 메소드를 사용하면 마치 레고 블록을 쌓듯이 차곡차곡 순서대로 원하는 층을 쌓아서 딥러닝 모델을 설계할 수 있습니다. (Dense, CNN, RNN, Embedding 등 모두 가능). Dense() 층의 units 매개변수에는 층별 노드 개수를 지정해주며, 활성화 함수는 activation 매개변수에서 지정해줍니다. 은닉층에서는 'Relu' 활성화함수를, Output 층에는 10개 classes에 대한 확률을 반환하므로 'softmax' 활성화함수를 사용하였습니다. 



# Sequential model

model = tf.keras.models.Sequential()


# Stacking layers

model.add(tf.keras.layers.Dense(units=128, activation='relu', input_shape=(28*28,)))

model.add(tf.keras.layers.Dense(units=10, activation='softmax'))




이렇게 층을 쌓아서 만든 DNN (Deep Neural Network) 모델이 어떻게 생겼는지 summary() 함수로 출력해보고, tf.keras.utils.plot_model() 메소드로 시각화해서 확인해보겠습니다. 



model.summary()

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 128)               100480    
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 101,770
Trainable params: 101,770
Non-trainable params: 0
_____________________________________


# keras model visualization

tf.keras.utils.plot_model(model, 

                          to_file='model_plot.png', 

                          show_shapes=True)


* 참고로 Keras의 tf.keras.utils.plot_model() 메소드를 사용하려면 사전에 pydot, pygraphviz 라이브러리 설치, Graphviz 설치가 필요합니다. 




  (3) 모델 컴파일 (Compiling the model)


위의 (2)번에서 딥러닝 모델을 구축하였다면, 이제 기계가 이 모델을 이해할 수 있고 학습 절차를 설정할 수 있도록 컴파일(Compile) 해줍니다. 


compile() 메소드에는 딥러닝 학습 시 사용하는 (a) 오차 역전파 경사하강법 시 최적화 알고리즘(optimizer), (b) 손실함수(loss function), 그리고 (c) 성과평가 지표(metrics) 를 설정해줍니다. 



model.compile(optimizer='sgd', 

              loss='sparse_categorical_crossentropy', 

              metrics=['accuracy']

)

 

* 참고로, input data의 다수 클래스에 대한 정답 레이블(ground-truth labels)을 바로 사용하여 손실함수를 계산할 때는 loss='sparse_categorical_crossentropy' 을 사용하며, One-hot encoding 형태로 정답 레이블이 되어있다면 loss='categorical_crossentropy' 를 사용합니다. 



Keras 는 위에서 처럼 optimizer='sgd' 처럼 기본 설정값을 사용해서 간단하게 코딩할수도 있으며, 만약 사용자가 좀더 유연하고 강력하게 설정값을 조정하고 싶다면 아래의 예시처럼 하위클래스(subclassing)을 사용해서 세부 옵션을 지정해줄 수도 있습니다. 



model.compile(

    optimizer=keras.optimizers.SGD(learning_rate=0.01, momentum=0.9, nesterov=True)

    loss=keras.losses.sparse_categorical_crossentropy

)

 




  (4) 모델 학습 (Training the model)


데이터 준비와 모델 구축, 그리고 모델 컴파일이 끝났으므로 이제 6만개의 훈련 데이터셋(Training set) 중에서 80% (4.8만개 데이터) 는 모델 학습(fitting)에, 20%(1.2만개 데이터)는 검증(validation)용으로 사용하여 DNN 모델을 학습해보겠습니다. (참고로, 1만개의 Test set은 모델 학습에서는 사용하지 않으며, 최종 모델에 대해서 제일 마지막에 모델 성능 평가를 위해서만 사용합니다.)


epochs = 5 는 전체 데이터를 5번 반복(iteration)해서 사용해서 학습한다는 의미입니다. 

verbose = 1 은 모델 학습 진행상황 막대를 출력하라는 의미입니다. ('1' 이 default이므로 생략 가능)

(0 = silent, 1 = progress bar, 2 = one line per epoch)

validation_split=0.2 는 검증용 데이터셋을 별도로 만들어놓지 않았을 때 학습용 데이터셋에서 지정한 비율(예: 20%) 만큼을 분할하여 (hyperparameter tuning을 위한) 검증용 데이터셋으로 이용하라는 의미입니다. 

batch_size=32 는 한번에 데이터 32개씩 batch로 가져다가 학습에 사용하라는 의미입니다. 


이번 포스팅은 Keras의 기본 사용법에 대한 개략적인 소개이므로 너무 자세하게는 안들어가겠구요, callbacks는 나중에 별도의 포스팅에서 자세히 설명하겠습니다. 


학습이 진행될수록 (즉, epochs 이 증가할 수록) 검증용 데이터셋에 대한 손실 값(loss value)은 낮아지고 정확도(accuracy)는 올라가고 있네요. 



model.fit(x_train, y_train, 

          epochs=5, 

          verbose=1, 

          validation_split=0.2)


Epoch 1/5
1500/1500 [==============================] - 1s 922us/step - loss: 0.7291 - accuracy: 0.8146 - val_loss: 0.3795 - val_accuracy: 0.8989
Epoch 2/5
1500/1500 [==============================] - 1s 811us/step - loss: 0.3605 - accuracy: 0.9007 - val_loss: 0.3081 - val_accuracy: 0.9145
Epoch 3/5
1500/1500 [==============================] - 1s 785us/step - loss: 0.3061 - accuracy: 0.9140 - val_loss: 0.2763 - val_accuracy: 0.9215
Epoch 4/5
1500/1500 [==============================] - 1s 772us/step - loss: 0.2749 - accuracy: 0.9228 - val_loss: 0.2519 - val_accuracy: 0.9290
Epoch 5/5
1500/1500 [==============================] - 1s 827us/step - loss: 0.2525 - accuracy: 0.9289 - val_loss: 0.2355 - val_accuracy: 0.9339
Out[14]:
<tensorflow.python.keras.callbacks.History at 0x7fc7920a1760>

 




  (5) 모델 평가 (Evaluating the model)


evaluate() 메소드를 사용하여 모델의 손실 값(Loss value) 과 컴파일 단계에서 추가로 설정해준 성능지표 값 (Metrics values) 을 Test set 에 대하여 평가할 수 있습니다. (다시 한번 강조하자면, Test set은 위의 (4)번 학습 단계에서 절대로 사용되어서는 안됩니다!)


Test set에 대한 cross entropy 손실값은 0.234, 정확도(accuracy)는 93.68% 가 나왔네요. (CNN 모델을 이용하고 hyperparameter tuning을 하면 99%까지 정확도를 올릴 수 있습니다.)



model.evaluate(x_test, y_test)

313/313 [==============================] - 0s 801us/step - loss: 0.2342 - accuracy: 0.9368
Out[15]:
[0.23418554663658142, 0.9368000030517578]





  (6) 예측 (Prediction for new data) 


위에서 학습한 DNN 모델을 사용해서 MNIST 이미지 데이터에 대해서 predict() 메소드를 사용하여 예측을 해보겠습니다. (별도의 새로운 데이터가 없으므로 test set 데이터를 사용함)


예측 결과의 0번째 데이터를 살펴보니 '7' 이라고 모델이 예측을 했군요. 



preds = model.predict(x_test, batch_size=128)


# returns an array of probability per classes

preds[0]

array([8.6389220e-05, 3.9117887e-07, 5.6002376e-04, 2.2136024e-03,
       2.7992455e-06, 1.4370076e-04, 5.6979918e-08, 9.9641860e-01,
       2.2985958e-05, 5.5149395e-04], dtype=float32)


# position of max probability

np.argmax(preds[0])

[Out] 7




실제 정답 레이블(ground-truth labels)과 비교해보니 '7' 이 정답이 맞네요. 



y_test[0]

[Out] 7


plt.imshow(x_test[0].reshape(28, 28))

plt.show()




  (7) 모델 저장, 불러오기 (Saving and Loading the model)


딥러닝 모델의 성과 평가 결과 기준치를 충족하여 현장 적용이 가능한 경우라면 모델의 요소와 학습된 가중치 정보를 파일 형태로 저장하고, 배포, 로딩해서 (재)활용할 수 있습니다. 


-  HDF5 파일로 전체 모델/가중치 저장하기


# Save the entire model to a HDF5 file.

model.save('mnist_dnn_model.h5') 




- 저장된 'mnist_dnn_model.h5' 모델/가중치 파일을 'new_model' 이름으로 불러오기


# Recreate the exact same model, including its weights and the optimizer

new_model = tf.keras.models.load_model('mnist_dnn_model.h5')


new_model.summary()

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 128)               100480    
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 101,770
Trainable params: 101,770
Non-trainable params: 0
_________________________________________________________________




TensorFlow 2.x 에서부터 High-level API 로 탑재되어 있는 Keras API 에 대한 간단한 MNIST 분류 DNN 모델 개발과 예측 소개를 마치겠습니다. 


* About Kerashttps://keras.io/about/

* Keras API Reference: https://keras.io/api/

* Save and Load model: https://www.tensorflow.org/tutorials/keras/save_and_load


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

2019년도 9월 말에 TensorFlow 2.0 버전이 공개되었으며. 이전의 TensorFlow 1.x 대비 (1) API Cleanup, (2) Eager execution, (3) No more globals, (4) Functions, not sessions 와 같은 변화가 있었습니다. (https://www.tensorflow.org/guide/effective_tf2)


이번 포스팅에서는 TensorFlow 1.x 에서의 지연 실행모드(Lazy execution), Global namespace, Session 호출 대비, TensorFlow 2.x 에서  Global namespace 나 Session 호출 없이, 즉시 실행모드(Eager execution)에 대해서 간단한 예제를 들어서 비교 설명해보겠습니다. 

(Keras API 와의 통합은 다음번 포스팅에서 소개하겠습니다.)


아래 비교 예제를 살펴보면 TensorFlow 2.x 에서의 코드가 한결 깔끔해져서 가독성도 좋아지고, 디버깅하기에도 편리해졌음을 알 수 있을 겁니다.  Python 프로그리밍에 이미 익숙한 사용자라면 TensorFlow 2.x 버전이 사용하기에 무척 친숙하게 느껴질 것입니다. 


아래에는 TensorFlow 1.12.0 버전과 TensorFlow 2.3.0 버전을 사용하여 TensorFlow 의 핵심 데이터 구조인 상수(constant), 변수(Variable), Placeholder 로 나누어서 TensorFlow 버전 간 비교해보겠습니다. 


  (1) 상수 (constant) 생성 및 연산 실행


 TensorFlow 1.x: 지연 실행 (lazy execution)

TensorFlow 2.x: 즉시 실행 (eager execution)


import tensorflow as tf


tf.__version__

'1.12.0'


a = tf.constant([0, 1, 2])

b = tf.constant([10, 20, 30])


# lazy execution

c = tf.add(a, b)

print(c)

Tensor("Add:0", shape=(3,), 
dtype=int32)



with tf.Session() as sess:

    print(sess.run(c))


[10 21 32]


import tensorflow as tf


tf.__version__

'2.3.0'


a = tf.constant([0, 1, 2])

b = tf.constant([10, 20, 30])


# eager execution

c = tf.add(a, b) # or c = a + b

print(c)

tf.Tensor([10 21 32], 
shape=(3,), dtype=int32)


TensorFlow 1.x 버전에서는 지연 실행 모드 (lazy execution) 가 기본 설정이므로 위의 더하기(add) 예제에서처럼 tf.constant() 로 두 개의 상수 배열을 만들고, tf.add(a, b)를 하면 바로 실행되는 것이 아니며, 계산 그래프와 Session을 생성하고 명시적으로 tf.Session().run() 을 호출하고 나서야 연산이 실행됩니다. 


반면에 TensorFlow 2.x 버전에서는 즉시 실행 모드 (eager execution) 가 기본 설정이므로 a, b 의 두 상수 배열을 생성한 후 tf.add(a, b) 를 하면 Session().run() 없이 바로 더하기 연산이 실행되어 그 결과를 반환합니다. 


TensorFlow 2.x 에서는 아래의 예처럼 numpy() 메소드를 사용해서 텐서를 numpy array 로 변환하여 텐서의 값에 접근할 수 있습니다. (참고로, 반대로 tf.convert_to_tensor(arr) 메소드를 사용하면 numpy array를 텐서 값으로 변환할 수 있습니다.)



# TensorFlow 2.x

# accessing the value of a Tensor

c_arr = c.numpy()

print(c_arr)

[10 21 32]

 




  (2) 변수 (Variable) 생성 및 연산 실행


TensorFlow 1.x

: lazy execution, session, global namespaces

TensorFlow 2.x

: eager execution, no session, no globals


import tensorflow as tf


tf.__version__

'1.12.0'


w = tf.Variable([[1.], [2.]])

x = tf.constant([[3., 4.]])


print(w)

<tf.Variable 'Variable:0' shape=(2, 1) dtype=float32_ref>


print(x)
Tensor("Const_2:0", shape=(1, 2), 
dtype=float32)


# lazy execution

w_x_matmul = tf.matmul(w, x)

print(w_x_matmul)

Tensor("MatMul:0", shape=(2, 2), 
dtype=float32)


with tf.Session() as sess:
    # initialization of variable
    sess.run(tf.global_variables_initializer())
    
    # runn tf.Session
    print(sess.run(w_x_matmul))


[[3. 4.]
 [6. 8.]]


import tensorflow as tf


tf.__version__

'2.3.0'


w = tf.Variable([[1.], [2.]])

x = tf.constant([[3., 4.]]) 


print(w)

<tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, numpy=
array([[1.],
       [2.]], dtype=float32)>



print(x)

tf.Tensor([[3. 4.]], shape=(1, 2), 
dtype=float32)



# eager execution

# no need for tf.global_variables_initializer()

w_x_matmul = tf.matmul(w, x)

print(w_x_matmul)

tf.Tensor(
[[3. 4.]
 [6. 8.]], shape=(2, 2), 
dtype=float32)


tf.Variable() 로 생성한 w 변수(Variable)와 tf.constant()로 생성한 상수로 tf.matmul() 메소드로 행렬곱 연산을 수행하여 보았습니다. 


TensorFlow 1.x 버전에서는 지연 연산 (lazy execution) 모드이므로 tf.matmul(w, x) 에서는 아무런 연산이 일어나지 않으며, Session을 열고 session.run()으로 명시적으로 호출할 때에야 비로서 행렬곱 연산이 실행됩니다. 더불어서, 변수를 Session 에서 실행시키기 전에 tf.global_variables_initializer() 를 먼저 호출해서 변수를 초기화(initialization) 해주어야 합니다. 


반면에 TensorFlow 2.x 버전에서는 tf.Variable(), tf.constant() 로 값을 할당해주면 바로 실행이 되어 변수와 상수가 생성이 됩니다. 그리고 tf.matmul() 메소드로 행렬곱 연산을 즉시 수행하여 결과값을 반환받습니다. (이때 tf.global_variables_initializer() 를 호출할 필요는 없습니다.) TenforFlow 1.x 대비 TensorFlow 2.x 가 코드가 한결 깔끔해졌습니다. 




  (3) tf.placeholder(...), feed_dict


Placeholder 는 TensorFlow에서 그래프를 실행할 때 사용자가 데이터를 주입할 수 있는 통로입니다. TensforFlow 1.x 버전에서는 아래처럼 Session을 실행할 때 feed_dict 매개변수를 사용하여 feed_dict = {placeholder: input} 처럼 사전형으로 값을 입력할 수 있습니다. 

반면에 TensorFlow 2.x 버전에서는 Session() 실행없이 바로 함수(function)에 입력값(input)을 넣어서 즉시 실행할 수 있습니다. 역시 코드가 한결 깔끔해졌습니다. 


# TensorFlow 1.x

outputs = session.run(f(placeholder), feed_dict={placeholder: input})


# TensorFlow 2.x

outputs = f(input)



TensorFlow 1.x:

Session, Placeholder & feed_dict 

 TensorFlow 2.x:

function


import tensorflow as tf


tf.__version__

'1.12.0'


# placeholder

a = tf.placeholder(tf.int32)

b = tf.placeholder(tf.int32)


# defining a summation function

def tf_sum(a, b):

    return a + b


# lazy execution

a_b_sum = tf_sum(a, b)


print(a_b_sum)

Tensor("add_1:0", dtype=int32)



with tf.Session() as sess:

    print(sess.run(a_b_sum, 

                  feed_dict = {a: [0, 1, 2], 

                               b: [10, 20, 30]}))


[10 21 32]



import tensorflow as tf


tf.__version__

'2.3.0'


a = tf.constant([0, 1, 2])

b = tf.constant([10, 20, 30])


# define a summation fuction

def tf_sum(a, b):

    return a + b


# eager execution

a_b_sum = tf_sum(a, b)


print(a_b_sum)

tf.Tensor([10 21 32], shape=(3,), 
dtype=int32)



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

행렬의 값이 대부분 '0'인 행렬을 희소행렬(Sparse matrix) 이라고 하며, 반대로 행렬의 값이 대부분 '0이 아닌 값'을 가지는 경우 밀집행렬(Dense matrix) 혹은 조밀행렬이라고 합니다. 


가령, 자연어처리(NLP)에서 텍스트를 파싱해서 TF-IDF 행렬을 만들다보면 대부분의 값은 '0'으로 채워져 있고 '0'이 아닌 값은 듬성듬성 들어있는 희소행렬을 주로 얻게 됩니다. 


희소행렬(Sparse matrix)의 경우 대부분의 값이 '0'이므로 이를 그대로 사용할 경우 메모리 낭비가 심하고 또 연산시간도 오래 걸리는 단점이 있습니다. 이런 단점을 피하기 위해 희소행렬을 다른 형태의 자료구조로 변환해서 저장하고 사용합니다. 


희소행렬을 저장하는 자료구조 4가지에는 


(a) Dictionary of keys(DOK): key (행번호, 열번호) 대 value (데이터) 매핑

(b) List of lists (LIL): 링크드 리스트 알고리즘을 이용한 저장 기법, 내용의 추가와 삭제가 용이하지만 CSR 대비 메모리 낭비가 큼

(c) Coordinate list (COO): (행, 열, 값) 튜플 목록 저장

(d) Compressed sparse row (CSR): 가로의 순서대로 재정렬하는 방법으로 행에 관여하여 정리 압축


가 있습니다. 

* reference: https://en.wikipedia.org/wiki/Sparse_matrix



이중에서 Compressed Sparse Row(CSR) 형태(format) 자료구조의 장점과 단점을 표로 정리해보면 아래와 같습니다. 


 CSR 자료구조의 장점

(Advantages of the CSR format)

CSR 자료구조의 단점

(Disadvantages of the CSR format)

  •  효율적인 산술 연산
     (예: CSR + CSR, CSR * CSR 등)
  • 효율적인 행 슬라이싱
    (efficient row slicing)
  • 빠른 행렬 벡터 곱
    (fast matrix vector products)
  • 느린 열 슬라이싱
    (slow column slicing) 
    --> Compressed Sparse Column format 고려
  • 희소성 구조의 변화 시 연산비용 큼
    --> LIL, DOK 고려



이번 포스팅에서는 희소행렬에 대해 이들 중에서도 SciPy 모듈의 csr_matrix() 메소드를 사용하여 


(1) NumPy 희소행렬을 SciPy 압축 희소 행(CSR) 행렬로 변환하기

   (Converting a NumPy sparse matrix to a SciPy compressed sparse row matrix)


(2) SciPy 압축 희소 행(CSR) 행렬을 NumPy 희소행렬로 변환하기 

   (Converting a SciPy compressed sparse row matrix to a NumPy sparse matrix)


하는 각 2가지 방법을 소개하겠습니다. 






  (1) NumPy array 행렬을 SciPy 압축 희소 행(CSR) 행렬로 변환하기

      (Transforming a NumPy matrix to a SciPy compressed sparse row matrix)


NumPy array 데이터형의 희소행렬을 SciPy 압축 희소 행(CSR) 행렬 (compressed sparse row matrix)로 만드는 3가지 방법을 아래의 arr 넘파이 배열을 예로 들어서 설명해보겠습니다. 


class scipy.sparse.csr_matrix(arg1, shape=None, dtype=None, copy=False)


(1-1) arr 넘파이 배열에 대해 => scipy.sparse.csr_matrix(arr)

(1-2) 값 data, '0'이 아닌 원소의 열 위치 indices, 행 위치 시작 indptr 

        => csr_matrix((data, indices, indptr), shape=(5, 4))

(1-3) 값 data, '0'이 아닌 원소의 (행, 열) 위치 => csr_matrix((data, (row, col)), shape=(5, 4))




Compressed Sparse Row matrix로 변환할 대상이 되는 NumPy array 예제 행렬인 'arr' 을 먼저 만들어보겠습니다. 



import numpy as np

from scipy.sparse import csr_matrix


arr = np.array([[0, 1, 0, 2], 

                [0, 3, 4, 5], 

                [0, 0, 0, 0], 

                [6, 0, 0, 7], 

                [0, 8, 0, 0]])


arr

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]])



(1-1) arr 넘파이 배열에 대해 => scipy.sparse.csr_matrix(arr)


NumPy 배열 (rank-2 ndarray), 희소행렬, 밀집행렬을 scipy.sparse.csr)matrix() 메소드 안에 넣어주면 되니 제일 쉬운 방법입니다. 



# converting NumPy array into SciPy Compressed Sparse Row matrix

csr_mat = csr_matrix(arr)


csr_mat

[Out] <5x4 sparse matrix of type '<class 'numpy.longlong'>'
	with 8 stored elements in Compressed Sparse Row format>

 



위에서 만든 'csr_mat' 이름의 5x4 sparse matrix (CSR format) 에서 특성값(attributes)으로서 

  - (a) csr_mat.indptr : 행렬의 '0'이 아닌 원소의 행의 시작 위치

  - (b) csr_mat.indices : 행렬의 '0'이 아닌 원소의 열의 위치

  - (c) csr_mat.data : 행렬의 '0'이 아닌 원소 값



print('-- Compressed Sparse Row matrix --')

print('indptr:', csr_mat.indptr)

print('indices:', csr_mat.indices)

print('data:', csr_mat.data)


-- Compressed Sparse Row matrix --
indptr: [0 2 5 5 7 8]
indices: [1 3 1 2 3 0 3 1]
data: [1 2 3 4 5 6 7 8]

 



이를 그림으로 좀더 알기 쉽게 표현을 해보면 아래와 같습니다. 헷갈리지 않고 좀더 알아보기에 편리하도록 NumPy array 행렬의 값(data)을 숫자가 아니라 영어 알파벳으로 바꾸어서 표시하였습니다. 



SciPy Compressed Sparse Row matrix 에서 

  - data 는 행렬의 '0'이 아닌 원소 값이므로 이해하기 어려운게 없습니다. 

  - indices 도 행렬의 '0'이 아닌 원소의 위치 (row, column) 에서 열(column) 위치(index) 배열 [1, 3, 1, 2, 3, 0, 3, 1 ] 이므로 어려울게 없습니다. 

  - indptr 은 저는 처음에 봤을 때는 이게 뭔가하고 유심히 보면서 좀 고민을 했습니다. ^^;  indptr은 행을 기준으로 했을 때 행별로 '0'이 아닌 원소가 처음 시작하는 위치의 배열입니다. 말로 설명하기 좀 어려운데요, 가령 위의 NumPy 배열 'arr'의 '0'이 아닌 원소의 위치 (행 row, 열 col) 배열(위 그림의 중간에 표시되어 있음)을 보면, 

'arr' 배열의 첫번째 행 [0, a, 0, b] 는 '0'이 아닌 원소의 (row, col) 배열0 위치에서 시작, 

               두번째 행 [0, c, d, e] 는 '0'이 아닌 원소의 (row, col) 배열의 2 위치에서 시작, 

               세번째 행 [0, 0, 0, 0] 는 '0'이 아닌 원소의 (row, col) 배열의 5 위치에서 시작, (비어있음) 

               네번째 행 [f, 0, 0, g] 는 '0'이 아닌 원소의 (row, col) 배열의 5 위치에서 시작, 

                        (--> 왜냐하면, 세번째 행의 모든 값이 '0' 이므로 같은 위치인 5에서 시작함)

               다섯번째 행 [0, h, 0, 0] 는 '0'이 아닌 원소의 (row, col) 배열의 7 위치에서 시작, 

               마지막으로, 'arr' 의 원소의 개수 8 에서 끝남.  


이렇게 indptr을 이용하는 이유는 행 기준의 '0'이 아닌 원소의 (row, col) 을 사용하는 것보다 데이터를 좀더 압축할 수 (즉, 줄일 수) 있기 때문입니다. 위의 예의 경우 row 기준으로 '0'이 아닌 원소의 (row, col)에서 row만 보면 [0, 0, 1, 1, 1, 3, 3, 4] 로서 [0, 0], [1, 1, 1], [3, 3] 처럼 같은 행에 두 개 이상의 '0'이 아닌 원소가 있으면 같은 행 숫자가 반복됩니다. 이럴 때 indptr 을 사용하면 [0, 2, 5, 5, 7, 8] 처럼 행 기준으로 '0'이 아닌 원소가 시작되는 row 위치만 가져오면 되므로 저장해야하는 정보량을 줄일 수 (압축) 있게 됩니다.   



(1-2) 값 data, '0'이 아닌 원소의 열 위치 indices, 행 위치 시작 indptr 

        => csr_matrix((data, indices, indptr), shape=(5, 4))


NumPy array 행렬이 없더라도, data, indices, indptr 입력값과 output 행렬의 형상(shape) 을 알고 있다면 SciPy Compressed Sparse Row matrix를 아래처럼 만들 수 있습니다.

(다만, indptr, indices 를 사람이 직접 입력하기에는 좀 어려운 면이 있어서 위의 (1-1) 방법보다는 좀 어려워보이네요.)



# converting NumPy array into SciPy Compressed Sparse Row matrix

indptr = np.array([0, 2, 5, 5, 7, 8]) # the location of the first element of the row.

indices = np.array([1, 3, 1, 2, 3, 0, 3, 1]) # column indices

data = np.array([1, 2, 3, 4, 5, 6, 7, 8])    # corresponding value


csr_mat2 = csr_matrix((data, indices, indptr), shape=(5, 4))

csr_mat2

[Out] <5x4 sparse matrix of type '<class 'numpy.int64'>'
	with 8 stored elements in Compressed Sparse Row format>



print('-- Compressed Sparse Row matrix 2 --')

print('indptr:', csr_mat2.indptr)

print('indices:', csr_mat2.indices)

print('data:', csr_mat2.data)


-- Compressed Sparse Row matrix 2 --
indptr: [0 2 5 5 7 8]
indices: [1 3 1 2 3 0 3 1]
data: [1 2 3 4 5 6 7 8]

 




(1-3) 값 data, '0'이 아닌 원소의 (행, 열) => csr_matrix((data, (row, col)), shape=(5, 4))


세번째는 행렬에서 '0' 이 아닌 원소의 값(data)과 (행, 열) 위치 (row_ind, col_ind), 그리고 행렬의 형상(shape) 을 입력해주는 방식입니다. (사람 입장에서는 이 (1-3) 방식이 위의 (1-2) 방식보다는 직관적으로 이해하기가 더 쉽기는 합니다.)



# converting NumPy array into SciPy Compressed Sparse Row matrix

row = np.array([0, 0, 1, 1, 1, 3, 3, 4])

col = np.array([1, 3, 1, 2, 3, 0, 3, 1])

data = np.array([1, 2, 3, 4, 5, 6, 7, 8])


csr_mat3 = csr_matrix((data, (row, col)), shape=(5, 4))

csr_mat3

[Out] <5x4 sparse matrix of type '<class 'numpy.longlong'>'
	with 8 stored elements in Compressed Sparse Row format>

 

print('-- Compressed Sparse Row matrix 3 --')

print('indptr:', csr_mat3.indptr)

print('indices:', csr_mat3.indices)

print('data:', csr_mat3.data)


-- Compressed Sparse Row matrix 2 --
indptr: [0 2 4 4 6 7]
indices: [1 3 1 2 0 3 1]
data: [1 2 3 4 5 6 7]


-- Compressed Sparse Row matrix 3 --
indptr: [0 2 5 5 7 8]
indices: [1 3 1 2 3 0 3 1]
data: [1 2 3 4 5 6 7 8]





  (2) SciPy 압축 희소 행(CSR) 행렬을 NumPy 행렬로 변환하기

     (Transforming a SciPy compressed sparse row matrix into a NumPy matrix) 


SciPy 압축 희소 행 행렬을 NumPy 행렬로 변환하기는 아래 2가지 메소드를 이용하면 매우 쉽습니다. 


(2-1) scipy.sparse.csr_matrix.toarray() 메소드

(2-2) scipy.sparse.csr_matrix.todense() 메소드



위에서 만든 'csr_mat', 'csr_mat2', 'csr_mat3' 세 개의 압축 희소 행(CSR) 행렬을 아래에서 원래의 NumPy array 배열로 변환해보니 모두 동일하게 제대로 변환이 되었네요. 


(2-1) scipy.sparse.csr_matrix.toarray() 메소드



# converting sparse matrix to NumPy array

csr_mat.toarray()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)


csr_mat2.toarray()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)


csr_mat3.toarray()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)





(2-2) scipy.sparse.csr_matrix.todense() 메소드


SciPy Compressed Sparse Row matrix를 원래의 행렬로 변환할 때 그게 희소행렬(Sparse matrix) 일 수도 있고 아니면 밀집행렬(Dense matrix) 일 수도 있기 때문에 메소드 이름을 csr_matrix.todense() 라고 하면 좀 오해의 소지도 있어서 썩 잘 지은 메소드 이름은 아니라고 생각하는데요, 어쨌든 반환된 후의 결과는 위의 csr_matrix.toarray() 와 동일합니다. 



csr_mat.todense()

[Out]
array([[0, 1, 0, 2],
       [0, 3, 4, 5],
       [0, 0, 0, 0],
       [6, 0, 0, 7],
       [0, 8, 0, 0]], dtype=int64)

 




  (3) 동일 위치에 중복된 원소값은 합산 (Duplicate entries are summed together.)


아래의 행렬처럼 (row, column) 이 (0, 0)인 위치에 5, 3 의 값이 중복되어 있고, (1, 1)인 위치에 2, 4 의 값이 중복되어 있는 Compressed Sparse Row matrix 데이터는 중복된 위치의 값을 더해주게 됩니다. 


  5 + 3

 0

 0

 0

 2 + 4

 0

 0

 0

 0



# Duplicate entries are summed together. 

row = np.array([0, 1, 1, 0])

col = np.array([0, 1, 1, 0])

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

csr_matrix((data, (row, col)), shape=(3, 3)).toarray()


[Out]
array([[8, 0, 0],
       [0, 6, 0],
       [0, 0, 0]], dtype=int64)

 



[ Reference ]

* SciPy 모듈 sparse.csr_matrix() 메소드

  : https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html

* Sparse matrix: https://en.wikipedia.org/wiki/Sparse_matrix



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

매일경제 MK 에서 부동산 정보업체 직방의 설문조사 결과를 인용해서 "2020년 주택 매입 전략 설문조사" 결과를 발표하였습니다. 그런데 직방의 설문조사는 표본설계가 잘못된 것으로 가려서 봐야합니다. 


올해 2020년 3월 4일자 매일경제 MK 기사의 헤드라인은 매우 자극적이게 "5000명 설문조사해보니 71% 올해 집 사겠다" 이네요. 




올해 202년 7월 31일자 매일경제 MK에 비슷한 내용으로 직방의 설문조사 결과를 인용하여 "설문조사 해보니 앞으로 주택 사고파는 건 투자보다..." 라는 제목으로 기사가 났습니다. 




내용은 위의 2020년 3월 4일자 기사와 비슷합니다. 직방 설문조사 결과 "내년 상반기까지 설문조사 결과 10명 중 6~7명은 주택을 사거나 팔 의향이 있다"고 하네요. 정말로? -_-?


* 기사 출처: 매일경제 MK. 2020.07.31 기사 




설문조사를 하는 이유는 모집단(population) 전체를 대상으로 조사를 하기에는 시간과 비용이 너무 많이 들기 때문에 "모집단을 대표할 수 있는 표본집단(sample)을 무작위로 추출"하여 적은 규모로 빠른 시간 안에 적은 비용으로 조사를 하고자 하기 때문입니다. 


그런데 위의 직방 설문조사의 경우 "모집단을 대표하는 표본"을 "무작위로 추출"한 것이 아니라, "부동산 구매에 엄~청 관심이 있는 집단"이 모여있는 "직방 앱 이용자"를 대상으로 무작위 추출이 아니라 "직방 앱 이용자 중에서 설문에 참여하고자 하는 의사가 있는 이용자가 자발적으로 설문에 참여"하여 이루어진 것입니다. 


이는 다른 예를 들어보자면, "파이썬 온라인 커뮤니티 회원을 대상으로 올해 파이썬 공부할 의향이 있습니까?" 라고 조사해보기, "중고자동차 온라인 커뮤니티 회원을 대상으로 올해 중고자동차를 매매할 의향이 있습니까?" 라고 조사해보기, "게임 동호회 회원을 대상으로 올해 온라인 게임을 할 의향이 있습니까?"" 라고 조사하는 것과 같은 맥락으로 참 어처구니 없는 조사인 겁니다. 



MK 신문기사에 보면 "올해부터 내년 상반기"까지 주택 매수의사가 있냐는 질문에 10명 중 7명이 있다(70.1%)라고 응답했다" 라고 직방 설문조사를 인용했는데요, 이거 굉장히 우습고 참 어처구니 없는 겁니다. 집 살 의향이 있어서 정보를 얻으려고 몰려드는 직방 앱 이용자 대상으로 주택 매수 의사를 물어보면 당연히 "있다"라고 답하지 않겠어요? 


상식적으로 생각해도 주택보급율이 50% 약간 못미쳤던거 같은데요, 전국민을 대상으로 물어본다면 10명 중 7명이 내년 상반기까지 주택 매수의사가 있다는게 말이 됩니까? 


부동산 정보제공 업체 직방이야 사람들이 부동산에 관심을 가지고 거래를 많이 해야 수익을 얻는 기업인 만큼 사람들이 부동산에 조바심을 내서 패닉 구매를 유도하려는 의도를 가지고 이런 설문조사 결과를 발표했을 수도 있겠다 싶은데요, 매일경제 MK 신문사는 이를 자극적인 제목의 신문기사로 꾸준히 기사를 내는 걸 보면 설문조사의 기본도 모르고 엉터리 설문조사를 인용하기나 하는 기자와 편집 데스크가 있는 곳이거나 아니면 직방과 같이 부동산에 바람넣으려는 불순한 의도를 가지고 기사를 내거나 겠지요. 어느 편이던지 간에 이런 엉터리 신문기사는 가려서 보시기 바랍니다. 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 값 배열에 가중치 배열을 곱해서 합한 가중합(weighted sum)을 구하는 3가지 방법을 소개하겠습니다. 


a 를 가중치, b 를 값 배열이라고 했을 때, 


(1) 내적을 이용한 가중합 계산: np.dot(a, b) or np.matmul(a, b)

(2) 브로드캐스팅(broadcasting)을 이용하여 가중치와 값을 원소끼리 곱한 후 합하는

     np.sum(a.reshape(5, 1) * b, axis=0)

(3) repeat()로 가중치를 값 배열 1축만큼 반복 생성한 후, 가중치와 값의 원소끼리 곱한 후 합하는, 

     np.sum(a.reshape(5, 1).repeat(3, axis=1) * b, axis=0)




먼저, numpy를 import하고, 예제로 사용할 가중치 배열 a와, 값의 행렬 b를 만들어보겠습니다. 



import numpy as np


# weights

a = np.array([0.5, 0.3, 0.1, 0.08, 0.02])


print('a shape:', a.shape)

a shape: (5,)


print(a)

[0.5  0.3  0.1  0.08 0.02]



# values

b = np.arange(15).reshape(5, 3)


print('b shape:', b.shape)

b shape: (5, 3)


print(b)

[[ 0  1  2]

 [ 3  4  5]

 [ 6  7  8]

 [ 9 10 11]

 [12 13 14]]

 




  (1) 내적을 이용한 가중합 계산: np.dot(a, b) 또는 np.matmul(a, b)


가장 편리한 방법은 np.dot() 또는 np.matmul() 메소드를 사용하여 내적(inner prodct, dot product)을 계산하는 것입니다. 이때 가중치 벡터 a 에 대해서는 형태 변환(reshape)을 할 필요가 없이 그대로 사용할 수 있습니다.  



np.dot(a, b)

Out[2]: array([2.46, 3.46, 4.46])


np.matmul(a, b)

Out[3]: array([2.46, 3.46, 4.46])

 




  (2) Broadcasting을 이용하여 가중치와 값을 원소끼리 곱한 후, axis=0으로 합하기


이번에는 위의 (1) 내적을 계산의 각 단계별로 분리해서 순서대로 해보겠습니다. 가중치 a와 값 b의 원소끼리 곱한 후에, axis=0을 기준으로 합할 것입니다. 


먼저, 가중치 a와 값 b를 원소끼리 곱하기 위해 가중치 a의 형태(shape)를 기존의 (5,)에서 a.reshape(5, 1) 을 적용하여 (5, 1) 의 형태로 변환을 해줍니다. 값이 들어있는 배열 b의 형태는 (5, 3) 이므로 가중치 배열 a의 (5, 1) 형태를 값 배열 b에 곱해주면 ==> 서로 형태가 같지 않으므로 numpy 는 가중치 a 배열 (5, 1) 을 (5, 3)으로 자동으로 형태 변환을 시켜서 값 배열 b 의 (5, 3) 형태와 동일하게 맞추어 주어 원소간 곱을 해줍니다. 이러한 기능을 브로드캐스팅(boradcasting) 이라고 합니다. 



# shape of a_rs and b are different

a_rs = a.reshape(5, 1)

print(a_rs.shape)

print(a_rs)

(5, 1)


print(b.shape)

(5, 3)


# multiply using boradcasting of a_rs

a_rs_b_mult = a_rs * b


print(a_rs_b_mult.shape)

(5, 3)


print(a_rs_b_mult)

[[0.   0.5  1.  ]

 [0.9  1.2  1.5 ]

 [0.6  0.7  0.8 ]

 [0.72 0.8  0.88]

 [0.24 0.26 0.28]]



# weighted sum

np.sum(a_rs_b_mult, axis=0)

Out[9]: array([2.46, 3.46, 4.46])



* numpy 배열들의 다른 차원의 배열 간 산술연산 시 Broadcasting 은 아래 포스팅을 참고하세요. 

https://rfriend.tistory.com/287




  (3) repeat()로 가중치를 반복 생성한 후, 가중치와 값을 원소끼리 곱한 후 합하기


위의 (2)번에서는 가중치 배열 a의 형태를 바꾼 후의 a_rs 배열과 값 b 배열을 곱할 때, 사람 눈에는 보이지않게 numpy가 알아서 자동으로 가중치 a_rs 배열 (5, 1) 형태를 브로드캐스팅(broadcasting)을 해주어서 (5, 3) 형태로 만들어서 원소끼리 곱해주었습니다. 




반면에, 이번 (3)번에서는 사람이 repeat(n, axis) 메소드를 사용해서 명시적으로 배열을 n번 만큼 axis 축을 기준으로 반복해주어서 (2)번의 브로드캐스팅의 역할을 수행해주는 것입니다. 


구현 관점에서 보면 브로드케스팅이 편리한 장점이 있고, 반면에 repeat() 메소드로 명시적으로 기입을 해주면 코딩하는 사람이 이해하기 쉬운 장점이 있습니다. 



# match the shape of a and b by repeatition 

a_rs_rp = a.reshape(5, 1).repeat(3, axis=1)


print(a_rs_rp.shape)

(5, 3)


print(a_rs_rp)

[[0.5  0.5  0.5 ]

 [0.3  0.3  0.3 ]

 [0.1  0.1  0.1 ]

 [0.08 0.08 0.08]

 [0.02 0.02 0.02]]



# multiplication of a_rs_rp and b per each elements

a_rs_rp_b_mult = a_rs_rp * b


print(a_rs_rp_b_mult.shape)

(5, 3)


print(a_rs_rp_b_mult)

[[0.   0.5  1.  ]

 [0.9  1.2  1.5 ]

 [0.6  0.7  0.8 ]

 [0.72 0.8  0.88]

 [0.24 0.26 0.28]]



# weighted sum

np.sum(a_rs_rp_b_mult, axis=0)

Out[17]: array([2.46, 3.46, 4.46])

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,