* <데이터 과학자와 데이터 엔지니어의 인터뷰 문답집>을 참고하여 작성되었습니다.

의사결정나무(Decision Tree) 뿌시기

의사결정나무란?

  • 의사결정나무는 데이터의 feature의 값에 따라서 분기를 나누면서 트리 구조를 만들고, 이를 이용해 분류나 회귀 문제를 푸는 모델이다.

의사결정나무의 장단점

  • 장점: 인간이 실제로 하는 판단과 과정이 비슷하기 때문에 직관적이고, 설명하기 쉽다.
  • 단점: 과적합의 위험이 크다.

의사결정나무가 만들어지는 방식

feature들을 통해서 분기를 나누면서 데이터를 예측하는 방식으로, 최적의 의사결정모델을 구하는 방식은 NP-hard 문제에 속한다. 따라서 의사결정나무는 분기를 나눌 feature를 정할 때, 휴리스틱 함수를 이용한다. 휴리스틱 함수는 위키백과의 정의에 따르면 "가용한 정보를 기반으로 각 분기 단계에서 어느 한 분기를 선택하기 위해 사용하는 다양한 탐색 알고리즘의 대안 함수"라고 정의 했다. 의사결정나무는 즉 모든 경우를 고려하여 최적의 트리를 만드는 방식이라기 보다는, 분기를 나눌때마다 휴리스틱 함수에 따라서 그때그때 최선의 feature를 선택하는 그리디 방식에 가깝다고 볼 수 있다.

의사결정나무를 만드는 데 사용되는 휴리스틱 함수는 최대정보이득, 최대정보이득비율, 지니계수가 있다.이 세 휴리스틱 함수는 각각 의사결정나무를 만드는 알고리즘인 ID3, C4.5, CART 알고리즘에서 사용된다.

  • 최대 정보 이득

최대 정보 이득을 구하기 위해서는 먼저 엔트로피에 대해서 알아야한다. 여기서 말하는 엔트로피는 열역학의 엔트로피는 아니고, 정보이론에서 말하는 엔트로피이다. 정보이론은 엔트로피라는 개념을 도입해 어떤 사건을 통해서 얻을 수 있는 "정보량"을 표현하고자 했다. 정보이론은 어떤 사건이 흔하게 발생하는 사건이면 그 사건은 새로운 정보를 주지 못하기 때문에 정보량이 낮고, 드물게 발생하는 사건이면 반대로 정보량이 높다고 한다. 엔트로피는 이 정보량의 평균을 의미한다.

이 정보량의 평균, 즉 엔트로피는 조건에 따라 변화하기도 한다. 아무런 데이터 없이 미래를 예측하는 것은 어렵다. 예상 가능한 미래의 사건이 수 없이 많을 것이고, 가능한 사건이 많다보니 각각의 확률도 낮아 결국 평균 정보량이 높아진다. 엔트로피가 높다. 하지만 과거의 데이터가 있다면 미래 예측은 좀 더 쉬워지게 된다. 이러한 사전정보로 인해 미래 예측에 대한 엔트로피가 낮아진다.

정보 이득(information gain)은 어떤 feature를 따라 분류했을 때 엔트로피가 낮아지는 정도라고 생각하면 된다. 이 경우엔 엔트로피를 최대한 많이 낮출 수 있는 feature를 찾아 분기를 만든다.

  • 최대 정보 이득비

정보 이득이 단순히 줄어드는 엔트로피의 값을 이용해 계산하는 것이라면, 정보 이득 비는 해당 feature에 따른 엔트로피와 그 feature가 주는 정보 이득 사이의 비율을 이용해서 계산한다. 이렇게 하는 이유는 feature의 종류의 수에 따른 불균형을 보정해주기 위한 것이다.

가능한 종류가 수없이 많은 feature의 경우, 당연하게도 정보이득이 굉장히 크다. 예를 들어 사람을 DNA 변수로 분류를 한다고 했을 때, 사람마다 전부 DNA가 다르기 때문에 각각 DNA의 확률도 낮아지게 되고 결국 정보 이득이 커질 수 밖에 없다.

하지만 이런 정보 이득은 실제 예측에서는 의미가 없다. 사람마다 전부 다른 DNA로 분류를 예측한다는 것은 과적합을 부를 뿐이다. 이런 단점을 보정해주기 위해 제안된 것이 최대 정보 이득비다. 앞서 언급한 DNA 정보같은 경우는 정보 이득이 상당히 크지만 DNA에 따른 엔트로피도 클 것이기 때문에 ''정보 이득 비''에서는 이것이 보정이 된다.

  • 지니 계수

지니계수는 CART에서 사용되는 휴리스틱 함수인데 CART는 다른 알고리즘과 약간 다른 것이 분기를 나눌때 항상 이진 트리의 형태로 나눈다. 예를 들어 성적이라는 변수에 상, 중, 하라는 값이 있을 때 다른 의사결정나무 알고리즘은 상, 중, 하로 분기를 나눌 수 있지만 CART는 성적이 상인 집합 또는 상이 아닌 집합 둘로만 나눌 수 있다. CART는 이런 식으로 모든 feature에 대해 두 집합으로 나눠보면서 지니계수를 계산하고 지니계수가 가장 낮은 feature를 선택한다.

지니계수를 사용하는 CART 알고리즘의 경우, 범주형 변수 뿐만 아니라 수치형 변수도 사용할 수 있다는 특징이 있다. 모든 분기에서 이진 분할을 사용하기 때문이다. (수치형 변수를 절반으로 나누면 된다) 또한 분류 뿐만 아니라 회귀 태스크에서도 지니계수 대신 MSE로 분기하는 방식으로 사용 가능하다.

왜 의사결정트리는 앙상블 모델에서 많이 쓰일까?

앙상블은 여러 모델을 융합하는 전략과 방법을 통칭해서 부르는 말이다. 앙상블의 주된 목적은 여러 모델의 결과를 사용해서 결과 값의 분산이나 바이어스를 줄이는 것이다. 그런데 이를 위해서는 여러 모델들이 다양한 값을 출력해야 한다. 모든 모델이 비슷한 값만 출력한다면 앙상블을 하나마나 의미가 없다.

이런점에서 의사결정트리가 앙상블에 최적인 모델이라고 볼 수 있다. 앙상블을 하는 과정에서 보통 학습 데이터에서 일정 비율의 샘플을 랜덤하게 추출해 여러개의 모델을 학습시키는 방법을 사용하는데 선형 회귀 등 다른 모델들은 학습 데이터가 조금 바뀌더라도 결과값이 크게 바뀌지 않는 반면 의사결정트리는 학습 데이터에 따라 트리 구조가 많이 바뀔 수 있다. 따라서 앙상블에서는 의사결정트리 모델을 많이 사용하게 된 것이다.

앙상블의 종류

앙상블에는 부스팅과 배깅이 있다. 부스팅은 여러개의 모델을 학습시킬 때, 전의 모델이 잘못 분류한 샘플에 대해 가중치를 줘서 다음 모델을 학습시킬 때 적용한다. 쉽게 말해서 부스팅에선 모델이 점점 발전하는 방식으로 학습이 된다고 할 수 있다. 이 방법은 마치 인간이 학습을 하는 과정과 비슷하다. 인간은 어떤 것을 처음 배울땐 서툴지만, 한번 두번 배우고나서 점점 과거에 실수했던 기억을 이용해 더 잘 배우게 된다.

반면 배깅의 경우는 모델들을 서로 독립적으로 유지시킨다. 대표적으로 랜덤 포레스트가 있다. 훈련 데이터셋에서 랜덤으로 샘플을 추출하고 각각 다른 데이터 셋 샘플을 만들어서 여러개의 모델을 훈련시킨다. 이렇게 훈련된 모델들은 서로 다른 데이터에서 학습되었으므로 상호 독립적이다. 이렇게 학습된 모델들은 투표(voting)를 이용해 최종 결과값을 결정하게 된다.

비유적으로 설명하면 부스팅은 직렬적, 배깅은 병렬적이다. 부스팅은 한 사람이 어떤 것을 여러 번 배우면서 익혀나가는 과정과 같다면 배깅은 여러 사람이 머리를 맞대고 의견을 모아 생각하는 것과 똑같다.

앙상블은 왜 사용하는가?

어떤 예측 모델의 불확실성을 설명할 때, 분산과 바이어스의 관점으로 설명할 수 있다. 분산은 훈련 데이터 셋이 가지고 있는 노이즈를 학습함에 의한 에러라면, 바이어스는 모델 자체의 성능에 따른 에러다.

부스팅 모델의 경우는 가중치를 이용해 모델을 점점 발전시키므로, 모델이 표현할 수 있는 범위를 늘려 모델의 "바이어스"에 따른 에러를 줄일 수 있다.

반면 배깅 모델의 경우는 훈련 데이터 셋에서 여러 샘플을 뽑아 여러 개의 모델을 학습시키므로 데이터 셋이 가지고 있는 노이즈가 희석 되어, 모델의 "분산"에 따른 에러를 줄일 수 있다.

앙상블의 결과를 병합하는 방법

기초 분류기의 결과를 병합하는 방법에는 투표(voting)와 스태킹(stacking)이 있다. 투표는 가장 많은 표를 얻은 결과를 출력하는 것이고, 스태킹은 모든 기초 분류기의 결과를 가중치에 따라 더한 값을 출력하는 것이다.

'Data Science' 카테고리의 다른 글

누락값(Missing value)를 다루는 방법들  (0) 2022.05.09
[논문 리뷰] ESRGAN  (0) 2022.03.25
[논문 리뷰] Attention is all you Need (transformer)  (0) 2022.03.04
[논문 리뷰] SRGAN  (0) 2022.02.18
빈도주의 vs 베이지안  (0) 2022.02.06

Introduction to flat files

Data Frame

  • pandas - 2차원의 구조 (행, 열)

Flat Files

  • 간단하고 만들기 쉬움
  • 색상이나 볼드체 서식이 없는 일반 텍스트
  • 한 라인마다 하나의 행을 의미
  • delimiter로 필드가 구분됨
  • 일반적으로 delimiter는 콤마,( csv)
  • pandas는 delimiter와 상관없이 모든 플랫 파일을 불러올 수 있다.
import pandas as pd

tax_data = pd.read_csv('us-tax-data-2016.csv')
tax_data.head(5)

Loading other flat files

  • delimiter를 sep= 인자에 넣어준다.
import pandas as pd
tax_data = pd.read_csv('us_tax_data_2016.tsv', sep='\t')

Modifying flat file imports

Limiting Columns

  • usecols 인자를 이용해 로드할 열을 선택한다.
  • 모든 열 이름이나 열 번호의 리스트를 사용할 수 있다.
col_names = ['STATEFIPS', 'STATE', 'zipcode', 'agi_stub', 'N1']
col_nums = [0, 1, 2, 3, 4]

# choose columns to load by name
tax_data_v1 = pd.read_csv('us_tax_data_2016.csv', usecols=col_names)

tax_data_v1 = pd.read_csv('us_tax_data_2016.csv', usecols=col_nums)

Limiting Rows

  • nrows 인자를 이용해서 불러올 행의 수를 제한한다.
  • skiprows 인자는 건너뛸 행의 수를 선택한다. 이 경우 header=None으로 설정해야 한다.

Assigning Columns Names

  • names 인자 사용
  • 모든 열의 이름의 리스트가 있어야 함
  • 일부 열만 바꾸려면 불러온 후에 수행해야 함
col_names = list(tax_data_first1000)

tax_data_next500 = pd.read_csv('us_tax_data_2016.csv', nrows=500, skiprows=1000, header=None, names=col_names)

Handing errors and missing data

Common Flat File import issue

  • 열 데이터 타입이 잘못됨
  • 값이 누락됨
  • pandas가 읽을 수 없는 값

Specifying data types

  • pandas는 자동으로 열의 데이터 타입을 유추한다.
  • 데이터 타입을 특정해주기 위해 dtype 인자를 사용한다. 각 키는 열 이름이고 값은 데이터 타입이 들어 있는 딕셔너리를 받는다.

Customizing Missing Data values

  • pandas는 자동적으로 결측치를 해석한다
  • na_value 인자를 이용해 missing values를 커스텀할 수 있다.
  • single value, list, dictionary가 들어갈 수 있다.
tax_data = pd.read_csv('us_tax_data_2016.csv', na_values={'zipcode':0})

Lines with errors

  • pandas가 읽을 수 없는 레코드
  • error_bad_lines=False인자를 추가하면 pandas는 에러가 나는 행을 건너 뛴다.
  • warn_bad_lines=True 인자를 추가하면 파싱할 수 없는 줄을 건너뛸 때, 메시지를 표시하게 된다.

Introduction to spreadsheets

spreadsheet

  • excel
  • 행과 열이 있는 데이터 셀이 있는 테이블에 데이터가 저장됨
  • 플랫 파일과 달리 자동으로 업데이트 되는 공식이 있을 수 있음
  • 여러 시트가 있을 수 있음

Loading spreadsheet

  • read_excel()
  • usecols'A:P'와 같은 excel 열문자의 범위 문자열도 허용함
survey_data = pd.read_excel('fcc_survey_with_headers.xlsx',
                           skiprows=2, 
                           usecols='W:AB, AR')

Getting data from multiple worksheets

Selecting sheet to Load

  • read_excel()는 기본으로 첫번째 sheet만 가져온다
  • sheet_name인자를 사용해서 달느 시트를 가져올 수 있다.
  • 시트의 이름이나 인덱스 넘버를 받는다(zero index).
  • read_excel()에 들어간 인자는 모든 시트에 적용된다.

Loading All sheet

  • sheet name에 None을 전달한다
  • OrderedDict 리턴

Putting It All Together

all_responses = pd.DataFrame()

# Key는 시트이름, value는 프레임
for sheet_name, frame in survey_responses.items():
    frame['Year'] = sheet_name
    all_responses = all_responses.append(frame)

Modifying imports: true/false type

Boolean Data

  • True/False
  • pandas는 True/False column을 float 데이터로 로드한다.
  • read_excel()dtype 인자로 어떤 column을 bool 타입으로 만들어 줄지 정할 수 있다.
  • 오직 True와 False 값만 가져야 하므로 N/A 값은 True로 인코딩된다.
  • 'Yes', 'No' 같이 0과 1이 아니고 값이 다를 경우 true_value 인자를 사용한다.
bool_data = pd.read_excel('fcc_survey_boolean.xlsx',
                         dtype={'AttendedBootcamp': bool,
                               ...},
                         true_values=['Yes'],
                         false_values=['No'])

Modifying imports: parding date

Date and Time Data

  • python의 datetime 데이터타입으로 저장된다.
  • pandas는 날짜/시간 데이터를 객체로 불러온다.
  • timespan을 계산하려면 datetime 데이터 타입을 이용해야 한다.
  • dtype이 아닌 parse_dates 인자를 사용한다!
# 시간 데이터가 여러 열에 분산되어 있을 경우 중첩 리스트 넣기
date_cols = ['Part1StartTime', 'Part1EndTime', [['Part2StartDate', 'Part2StartTime']]]

survey_df = pd.read_excel('fcc_survey.xlsx',
                         parse_dates=date_cols)

# 컬럼의 이름을 지정해주고 싶으면 dict
date_cols = {'Part1Start': 'Part1StartTime',
            'Part1End': 'Part1EndTime',
            'Part2Start':['Part2StartDate',
                         'Part2StartTime']}

Non-Standard Dates

  • parse_date는 pandas가 이해할 수 있는 형식에만 작동함
  • to_datetime() 인자를 이용
  • 데이터 프레임과 column을 format 형식을 이용해 datetime format으로 바꿔줌
  • strftime.org
format_string = '%m%d%Y %H:%M:%S'
survey_df['Part2EndTime'] = pd.to_datetime(survey_df['Part2EndTime'],
                                          format=format_string)

Introduction to databases

Relational Databases

  • table로 정리된 데이터
  • row와 column으로 이루어짐
  • primary key를 이용해 테이블들이 연결될 수 있음
  • 스프레드 시트나 플랫파일보다 더 많은 데이터를 처리하고 더 많은 동시 사용자를 지원함
  • SQL을 통해 데이터베이스와 인터페이스

Common realational databases

  • SQLite
    • 컴퓨터 파일로 저장되는 데이터 베이스

Connecting to Databases

  • 데이터 베이스로 연결할 수 있는 way를 만든다
  • database를 쿼리한다.

Creating a Database Engine

  • SQLAlchemy
    • create_engine()은 데이터베이스 커넥션을 다룰 수 있게 해준다.
      • database에 대한 문자열 URL이 필요함
      • sqlite:///filename.db

Quering Database

  • 데이터 베이스에서 데이터를 로드하기 위해pd.read_sql(query, engine) 사용

Refining imports with SQL queries

SELECTing Columns

  • SELECT [column names] FROM [table name];
  • WHERE을 이용해 필터링
    • 표준 연산자
      • =, >, >=, <, <=, <>
    • 문자열 필터링
    • AND, OR과 사용 가능

More complex SQL queries

  • Get unique values for one or more columns with SELECT DISTINCT
  • Aggregate function
    • sum, avg, max, min
    • count
    • group by

Loading multiple tables with join

  • database records는 unique한 identifier가 있다.
  • JOIN ON을 이용해 서로 다른 테이블을 identifier로 조인할 수 있다.
  • SQL 키워드의 순서
    • SELECT
    • FROM
    • JOIN
    • WHERE
    • GROUP BY

Introduction to JSON

  • common web data format(자바스크립트)
  • 테이블 형식이 아님
  • record가 모두 동일한 컬럼을 가질 필요가 없음
  • 속성-값 쌍을 가진 collection(dictiㅐnary와 비슷)
  • 중첩 가능

Reading JSON Data

  • read_json()
    • json 파일 경로나 json 문자열을 넣을 수 있음
    • dtype 인자로 데이터타입 명시
    • orient 인자로 레코드들이 어떻게 정렬될 것인지 정한다.(record oriented or column oriented)

Introduction to APIs

API

  • 어플리케이션이 다른 프로그램들과 어떻게 소통할지 정의한다.
  • 데이터베이스 구조를 몰라도 api를 통해 데이터를 얻을 수 있다.

request

  • request.get(url_string):url에서 데이터 가져오기
    • params: 딕셔너리를 받아 api에 값을 전달한다.
    • header: 딕셔너리를 받아 사용자 인증키를 전달한다.
  • response 객체를 리턴하며 response.json()메소드를 사용해야 결과값만 가져올 수 있다.

Working with nested JSONs

Nested JSONs

  • value 자체가 객체인 경우 JSON이 중첩된다.
  • pandas.io.json
    • JSON을 읽고 쓰기 위한 판다스의 서브모듈
    • json_normalize(): 딕셔너리나 딕셔너리의 리스트를 받아 데이터 프레임을 반환한다.
      • record_path인자는 파일경로에 폴더를 나열하는 것과 같이 중첩 데이터에 대한 속성 문자열 또는 문자열 목록을 사용한다.
      • meta: 속성의 리스트를 받아 어떤 속성을 로드할 것인지 결정
      • meta_prefix: 속성의 출처를 명확히하고 열 이름이 중복되지 않기 위해서 접두사 사용

Combining multiple datasets

  • appending: 데이터 프레임을 다른 데이터프레임에 row로 추가한다. (df1.append(df2, ignore_index=True))
  • merging: dataset을 column을 더해 합친다. merge()는 SQL의 조인과 같은 메소드다.
    • df.merge()
      • on
      • left_on, right-on

'Data Science > [DSF] Data engineering' 카테고리의 다른 글

What is Data Engineering?  (0) 2022.05.15
Data engineering for everyone  (0) 2022.04.10

What is data engineering?

In comes the data engineer

  • 데이터가 흩어져 있음
  • 데이터베이스가 어플리케이션에 최적화 되어 있고 분석에 최적화되어 있지 않음
  • 레가시 코드들이 데이터를 손상시킴

What is parallel

  • 메모리의 프로세싱 파워의 이득
  • 아이디어: task를 subtask로 나누어 여러 컴퓨터에 분산시킨다.

병렬처리의 리스크

  • 통신 오버헤드
    • task가 너무 작을 때
    • 프로세싱 유닛이 적을 때
    • 이 오버헤드로 인해 프로세싱 유닛이 늘어나도 속도가 선형으로 증가하지 않는다(pararell slowdown)

병렬 처리 구현방법

multiprocessing.Pool: 동일한 컴퓨터의 여러 코어에 작업을 분산처리

from multiprocessing import Pool
import pandas as pd

def take_mean_age(year_and_group):
    year, group = year_and_group
    return pd.DataFrame({'Age': group['Age'].mean()}, index=[year])

with Pool(4) as p:
    result = p.map(take_mean_age, athlete_events.groupby('Year'))

result_df = pd.concat(result)

dask: 추상화 제공 프레임워크

import dask.dataframe as dd

# partition dataframe into 4
athelte_events_dask = dd.from_pandas(athlete_events, npartitions=4)

result_df = athlete_events_dask.groupby('Year').Age.mean().compute()

Parallel computation frameworks

Hadoop

  • HDFS: 분산 파일 시스템
  • MapReduce: 빅데이터 처리 시스템

Hive

  • 하둡 에코시스템의 최상위 계층
  • SQL의 변형인 Hive SQL을 사용하여 구조화된 방식으로 쿼리를 할 수 있다.
  • 다른 데이터 처리 툴과 통합됨

Hive: an example

  • 일반 SQL 쿼리와 비슷하지만 분산처리로 작동한다
SELECT year, AVG(age)
FROM views.athlete_events
GROUP BY year 

Spark

  • 맵리듀스는 job 사이에 디스크를 많이 쓰지만, Spark는 가능한 한 메모리에서 작동한다.
  • 맵리듀스의 단점 보완

Resilient distributed datasets(RDD)

  • spark에서 사용
  • 여러개의 노드에 분산되어 저장하는 데이터
  • 데이터프레임과 달리 컬럼에 이름이 없음
  • 튜플의 리스트와 비슷함
  • Operation
    • Transformation: .map() or .filter() -> RDD 리턴
    • Action: .count() or .first() -> single result

PySpark

  • 스파크의 프로그래밍 언어 인터페이스
  • Python interface
  • pandas와 유사함

PySpark: an example

(athlete_events_spark
    .groupBy('Year')
    .mean('Age')
    .show())

Workflow scheduling frameworks

An example pipeline

  • 어떻게 스케줄링?
    • 매일 수동으로: 확장하기 어려움
    • cron: dependency 처리가 어려움(작업의 순서...)

DAGs(Directed Acyclic Graph)

  • Set of nodes
  • Directed edges
  • No cycles

The tools for the job

  • Linux's cron
  • spotify's Luigi
  • Airflow

Airflow: an example

# Create the DAG object
dag = DAG(dag_id='example_dag', ..., schedule_interval="0 * * * *")

# Define operations
start_cluster = StartClusterOperator(task_id='start_cluster', dag=dag)
ingest_customer_data = SparkJobOperator(task_id='ingest_customer_data', dag=dag)
ingest_product_data = SparkJobOperator(task_id='ingest_product_data', dag=dag)
enrich_customer_data = PythonOperator(task_id='enrich_customer_data', ..., dag=dag)

# Set up dependecy flow
start_cluster.set_downstream(ingest_customer_data)
ingest_customer_data.set_downstream(enrich_customer_data)
ingest_product_data.set_downstream(enrich_customer_data)

Extract

  • 의미: 데이터 스토리지에서 데이터를 추출하는 것

Extract from text files

  • unstructured
  • flat file은 row와 column으로 이루어져 있다.(ex. csv, tsv)

JSON

  • JavaScript Object Notation
  • semi-structured
  • number, string, boolean, null
  • array, object
  • python의 딕셔너리와 비슷, 매핑이 잘 됨(json 패키지)
  • 많은 웹서비스가 이 형태로 데이터를 전달함

Data on the Web

  • request

    • ex. Browss to Google
    • Request to Google server
    • Google responds with Web page
  • APIs

    • 데이터를 JSON 포맷으로 보냄
    • API: application programming interface
  • database

    • 기존 애플리케이션 데이터베이스에서 추출
      • 많은 Transaction(행 삽입)
      • OLTP
      • row-oriented
    • Analytical database
      • OLAP
      • Column-oriented
  • connection string/uri

    • postgresql://[user[:password]@][host][:port]

    • import sqlalchemy
      connection_uri = 'postgresql://repl:password@localhost:5432/pagila'
      db_engine = sqlalchemy.create_engine(connection_uri)
      
      import pandas as pd
      pd.read_sql('SELECT * FROM customer', db_engine)

Transform

  • selection of attribute (ex. email)
  • Transaltion of code values (ex. New York -> NY)
  • Data validation (ex. date input in 'created_at')
  • Splitting columns into multiple columns(email -> user, domain)
  • Joining from multiple sources

pyspark

import pyspark.sql

spark = pyspark.sql.SparkSession.builder.getOrCreate()
spark.read.jdbc('jdbc:postgresql://localhost:5432/pagila',
               properties={'user':'repl', 'password':'password'})

# jdbc: Spark가 여러 관계형 데이터베이스에 연결하는 데 도움이 되는 소프트웨어

example: join

customer_df # pyspark dataframe with customer data
rating_df # pyspark dataframe with rating data

# Groupby ratings
ratings_per_customer = ratings_df.groupBy('customer_id').mean('rating')

# Join on Customer ID
customer_df.join(
rating_per_customer,
customer_df.customer_id=ratings_per_customer.customer_id)

Loading

Analytics or applications databases

  • Analytics
    • Aggregate queries
    • online analysis processing(OLAP)
    • column-oriented
    • columns subset에 대해 쿼리함
    • 병렬화에 적합
  • Applications
    • lots of transaction
    • online transaction processing(OLTP)
    • row-oriented
    • 레코드를 넣고 빼는 것이 쉽고 빠름

MPP Databases

  • Massively parallel processing database
  • 쿼리가 하위작업으로 분리되어 여러 노드로 분산됨

example: Redshift

# Pandas .to_parquet() method
df.to_parquet('./s3://path/to/bucket/customer.parquet')
# pyspark .write.parquet() method
df.write.parquet('./s3://path/to/bucket/customer.parquet')
COPY customer
FROM 's3://path/to/bucket/customer.parquet'
FORMAT as parquet

Load to PostgrSQL

# Transformation on data
recommendations = transform_find_recommecdatins(ratings_df)

# Load into PostgreSQL database
recommendations.to_sql('recommendations',
                      db_engine,
                      schema='store',
                      if_exists='replace')

Putting it all together

  • ETL 과정을 하나의 함수로 초기화
def extract_table_to_df(tablename, db_engine):
    return pd.read_sql('SELECT * FROM {}'.format(tablename), db_engine)

def split_columns_transform(df, column, pat, suffixes):
    # Converts column into str and splits it on pat

def load_df_into_dwh(film_df, tablename, schema, db_engine):
    return pd.to_sql(tablename, db_engine, schema=schema, if_exists='replace')

db_engine = {...}

def etl():
    film_df = extract_table_to_df('film', db_engine['store'])
    film_df = split_columns_transform(film_df, 'rental_rate', '.', ['_dollar', '_cents'])
    load_df_into_dwh(film_df, 'film', 'store', db_engine['dwh'])

Airflow refresher

  • python으로 작성된 워크플로우 스케줄러
  • 비선형 그래프 작성 가능
  • DAGs

scheduling with DAGs in Airflow

from airflow.models import DAG
from airflow.operators.python_operator import PythonOperator

dag = DAG(dag_id='etl_pipeline',
         ...,
         schedule_interval='0 0 * * *') # cron 표현식

etl_task = pythonOperator(task_id='etl_task',
                         python_callable=etl,
                         dag=dag)

etl_task.set_upstream(wait_for_this_task)
# wait_for_this_task가 끝난 후 실행
  • cron 표현식: 분, 시간, 일, 월, 요일

From rating to recommendation

Our recommendation transform

  • 사용자가 대부분 높게 평가한 기술의 코스를 추천
  • 이미 평가 한 코스를 추천하지 않음
  • 가장 높은 등급의 코스 3개를 추천

Scheduling daily jobs

The loading phase

# pandas dataframe to sql
recommendations.to_sql(
"recommendations",
db_engine,
if_exists="append")

전체 ETL

def etl(db_engines):
    # Extract the data
    course = extract_course_data(db_engines)
    rating = extract_rating_data(db_engines)

    # Clean up courses data
    courses = transform_fill_programming_language(courses)

    # Get the average course rating
    avg_course_rating = transform_avg_rating(rating)
    courses_to_recommend = transform_courses_to_recommend(
    rating,
    courses,
    )

    # Calculate the recommendation
    recommendations = transform_recommendations(
    avg_course_rating,
    course_to_recommend,
    )

    # Load the recommendations into the database
    load_to_dwh(recommendations, db_engine)

Creating the DAG

from airflow.models import DAG
from airflow.operators.python_operator import PythonOperator

dag = DAG(dag_id='recommendations',
         scheduled_interval='0 0 * * *')

task_recommendations = PythonOperator(
task_id='recommendations_task',
python_callable=etl)

'Data Science > [DSF] Data engineering' 카테고리의 다른 글

Introduction to flat file  (0) 2022.05.15
Data engineering for everyone  (0) 2022.04.10

데이터가 누락되는 종류

  • 무작위로 누락: 변수의 종류와 상관없는 누락

  • 다른 변수와 관련있는 누락: 해당 변수가 아닌 다른 변수의 영향을 받아서 누락된 경우 (ex. 설문조사의 뒷면이 있는 지 모르고 응답하지 않아서 누락된 경우)

  • 해당 변수와 관련있는 누락: 누락된 해당 변수에 관련해서 누락된 경우(어떤 설문조사에서 일부 질문에 정치적인 성향 등의 이유로 채우지 않았을 경우)

  • 첫번째, 두번째는 제거하는 것이 좋지만, 세번째 경우 단순 제거하면 모델이 편향 될 가능성이 있다.

누락값 해결책

  • 삭제: 데이터 버리기(row) or 변수 버리기(col)

    • 단점: 모델이 편향될 수 있으며, 중요한 변수가 버려질 수 있음
  • 대표값(mean, median, mode)으로 대체

    • 단점: 대체한 변수가 다른 변수와 상관관계가 있을 때, 상관관계를 고려하지 않음, 데이터 전체의 분산이 줄어들게 됨(평균), 데이터 전체에 편향이 생김(최빈값)
  • 다중 대체법(multiple imputation, MICE)

    • 과정

      1. 대체하고자 하는 누락값을 비우고, 다른 누락값은 대표값으로 채운다.
      2. 다른 변수들을 X로하고, 대체하고자 하는 누락값이 속하는 변수를 Y로하는 선형회귀모델을 이용해 누락값을 채운다.
      3. 다른 누락값에 대해서도 똑같이 적용한다.
      4. 대체 전 값과 대체 후의 값의 차이가 0이 되도록 이 과정을 반복한다.
  • KNN 사용, 변수를 통해 가까운 데이터 K개를 골라 이 데이터들의 평균을 사용함

    • 단점: 오래걸림, outlier에 민감함, 변수의 scale을 조절해줘야 함

출처: https://subinium.github.io/missing-data-handling/

'Data Science' 카테고리의 다른 글

[ML] 의사결정나무(Decision Tree) 뿌시기  (0) 2022.05.21
[논문 리뷰] ESRGAN  (0) 2022.03.25
[논문 리뷰] Attention is all you Need (transformer)  (0) 2022.03.04
[논문 리뷰] SRGAN  (0) 2022.02.18
빈도주의 vs 베이지안  (0) 2022.02.06

Data engineering for every one[Data camp]

Data Workflow

  • 데이터 수집 및 저장 -> 데이터 엔지니어링
  • 데이터 정리(preparation)
  • 탐색, 시각화
  • 실험, 예측

데이터 엔지니어

  • 데이터 엔지니어가 제공하는 것

    • 정확한 데이터

    • 올바른 형태로

    • 올바른 사람들에게

    • 효율적인 형태로

  • 데이터 엔지니어가 하는 일

    • 데이터 수집
    • 데이터베이스 최적화
    • 데이터 손상 관리
    • 데이터베이스 아키텍처 개발, 구성, 테스트 및 유지

데이터 엔지니어 vs 데이터 사이언티스트

  • 데이터 엔지니어
    • ingest and store data
    • set up database
    • build data pipeline
    • strong software skills
  • 데이터 사이언티스트
    • exploit data
    • access database
    • use pipeline output
    • strong analytical skills

Data pipeline

  • 데이터 수집
  • 처리
  • 저장
  • 파이프라인이 필요하다!
  • 모든 station을 자동화
  • 정확하고 relevant한 데이터를 제시간에 제공
  • 데이터 파이프라인은 데이터가 조직 전체에 흐르게 한다
  • 자동화한다
    • 추출
    • 변환
    • 결합
    • 검증
    • 로드
  • 줄인다
    • 인간의 개입
    • 에러
    • 걸리는 시간

ETL

  • 데이터 파이프라인을 디자인하는 프레임웍
    • Extract
    • Transform
    • Load
  • 데이터를 한 시스템에서 다른 시스템으로 이동시킨다.

데이터 스트럭쳐

구조화된 데이터

  • 쉽게 검색하고 구성
  • 행과 열의 엄격한 구조
  • 각 열은 특정 데이터 타입이 있음
  • 관계를 형성하기 쉽게 그룹화됨
  • 관계형 데이터 베이스에 저장됨
  • 거의 20%의 데이터가 구조화된 데이터다
  • SQL을 사용하여 만들어지고 쿼리된다.

semi-structured data

  • 검색하고 구조화하기 쉬움
  • 구조에 덜 엄격함
  • 여러가지 타입가능
  • 그룹화될 수 있지만 더 많은 작업이 필요함
  • NoSQL database: Json, XML, YAML

Unstructured data

  • 행과 열 형식으로 표현될 수 없는 데이터
  • 검색 및 구조화가 어려움
  • 텍스트, 사운드, 사진, 비디오
  • 일반적으로 데이터 레이크에 저장됨
  • 거의 대부분의 데이터는 unstructure다
  • 매우 가치있을 수 있음

Adding some structure

  • unstructured 데이터를 검색하고 구조화하기 위해 AI를 사용

  • semi-structure로 만들기 위해 정보를 더한다.(장르, 태그...)

SQL 데이터베이스

SQL

  • Structured Query Language
  • RDBMS에서 쿼리를 하는 데 사용됨
  • 한번에 많은 레코드에 엑세스할 수 있고 그룹화, 필터링, 집계할 수 있다.
  • 영어에 가까워서 쓰기와 이해가 쉽다
  • 데이터 엔지니어는 데이터베이스를 유지하고 만드는데 사용
  • 데이터 과학자는 SQL를 이용해 쿼리함
CREATE TABLE employee (
    employee_id INT,
    first_name VARCHAR(255),
    last_name VARCHAR(255),
    role VARCHAR(255),
    team VARCHAR(255),
    full_time BOOLEAN,
    office VARCHAR(255)
);

데이터베이스 스키마

  • 데이터 베이스는 많은 테이블로 구성된다.
  • 데이터 베이스 스키마는 테이블이 어떻게 related 되는지 관리한다.

데이터 웨어하우스 & 데이터 레이크

  • 데이터 레이크
    • 모든 raw data 저장
    • 정형, 반정형, 비정형 모든 타입
    • 비용이 적게 듬
    • 분석하기 어려움
    • 데이터 카탈로그를 최신상태로 유지해야 함
    • 데이터 사이언티스트에게 쓰임
    • 큰 데이터, 실시간 데이터
  • 데이터 웨어하우스
    • 특수 목적을 위해 특정한 데이터를 수집
    • 상대적으로 작음
    • Structured data 상태로 저장하기 때문에 업데이트에 비용이 듬
    • 분석에 최적화되어 잇음
    • data analytics에게 주로 쓰임
    • read only queries

data catalog for data lakes

  • 데이터의 출처
  • 어떻게 사용?
  • 누가 유지 관리를 하는가
  • 얼마나 업데이트가 자주 되는가
  • 데이터 거버넌스(가용성, 사용성 무결성, 보안)
  • 프로세스의 재현을 보장

Database vs data warehouse

  • Databasse
    • 일반적인 용어
    • organized data stored and accessed on computer
  • Data warehouse
    • 데이터베이스의 한 종류

Processing data

  • 원시데이터를 의미있는 정보로 바꾸는 것

Data processing value

  • 원하지 않는 데이터 제거
  • 메모리, 프로세스, 네트워크 비용을 최적화 한다.
  • 사용하기 쉬운 타입으로 데이터를 변경한다.
  • 데이터를 구조화 함
  • 특정 스키마와 구조를 갖게 함
  • 생산성 향상

데이터 엔지니어가 data processing을 하는 법

  • 데이터 조작, 정리
    • 자동화될 수 있음
  • 데이터를 적절이 구조화 된 데이터베이스에 저장
  • create views on top of database tables(뷰 생성)
  • 데이터베이스 성능 최적화 (ex. 데이터 인덱싱)

Scheduling data

  • 데이터 프로세싱의 모든 과정에 적용 가능
  • 데이터 엔지니어링 시스템의 접착제
  • task가 특정 순서로 작동되고 모든 종속성을 해결되도록 한다.

Manual, time, sensor scheduling

  • 수동으로
  • 특정시간, 조건(sensor scheduling)에 처리가 실행되도록 자동화
  • sensor scheduling은 항상 조건을 확인하기위해 수신 대기 상태에 있어야 하는 단점
  • 수동과 자동의 합성

Batches and stream

  • Batches

    • 한 인터벌마다 기록을 모음
    • 더 저렴
  • Streams

    • 개별 데이터를 바로 전송(ex. 유저 가입)
  • 스케줄링 툴: airflow, Luigi

Parallel computing

  • 최신 데이터 처리 툴의 기초
  • 메모리와 프로세싱 파워를 늘려줌
  • 데이터 처리 툴이 작업을 할때 task를 더 작은 subtask로 나누고 이 것들을 여러 컴퓨터에 분산시킴

pararell computing의 장점/e단점

  • 장점
    • 더 많은 프로세싱 파워
    • reduce memory footprint
  • 단점
    • 데이터를 이동시키는 것이 비용을 발생시킴
    • 통신 시간이 필요

Cloud computing

  • severs on premise
    • 직접 구입
    • 공간 필요
    • 전기요금, 유지 관리 비용
    • 피크타임에 적절한 프로세싱 파워
  • servers on cloud
    • 대여
    • 공간이 필요 없음
    • 필요할때만 자원 사용
    • 서버와 사용자가 가깝게 함
    • 데이터베이스 안정성

cloud computing for data storage

  • 데이터 베이스 안정성
  • 민감한 데이터에 대한 위험관리

AWS

  • File Storage: S3
  • computation: EC2
  • database: RDS

Microsoft Azure

  • File Storage: Blob Storage
  • computation: Virtual Machine
  • database: SQL Database

Google Cloud

  • File Storage: google cloud storage
  • computation: google compute engine
  • database: google cloud SQL

Multi cloud

  • 장점
    • 한 공급자에 대한 의존도를 줄임
    • 비용 절감
    • 법률
    • 재난에 대비
  • 단점
    • 클라우드 공급자는 서비스를 통합해 가두고자 한다
    • 호환성
    • 보안 및 거버넌스 관리를 어렵게 한다.

'Data Science > [DSF] Data engineering' 카테고리의 다른 글

Introduction to flat file  (0) 2022.05.15
What is Data Engineering?  (0) 2022.05.15

- 주의: 이 포스팅은 제가 읽고 이해한대로 작성하는 논문리뷰이며 학문적인 오류나 오역이 있을 수 있습니다.

논문링크: https://arxiv.org/abs/1809.00219

 

ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks

The Super-Resolution Generative Adversarial Network (SRGAN) is a seminal work that is capable of generating realistic textures during single image super-resolution. However, the hallucinated details are often accompanied with unpleasant artifacts. To furth

arxiv.org

참고 코드 링크: https://github.com/eriklindernoren/PyTorch-GAN/blob/master/implementations/esrgan/esrgan.py

 

GitHub - eriklindernoren/PyTorch-GAN: PyTorch implementations of Generative Adversarial Networks.

PyTorch implementations of Generative Adversarial Networks. - GitHub - eriklindernoren/PyTorch-GAN: PyTorch implementations of Generative Adversarial Networks.

github.com

 

서론

 SRGAN 이전까지 Super-resolution task는 PSNR을 최적화하는 방향으로 이루어졌다. PSNR은 MSE를 기반으로 한, 이미지가 얼마나 손실되었는지 판단하는 지표인데 딥러닝 네트워크를 PSNR 최적화에 초점을 맞춰 설계하면 결과물 이미지가 디테일이 뭉개져서 과하게 부드러워지는(over-smoothed) 현상이 발생하게 된다.

SRGAN은 GAN 네트워크를 Super-resolution task에 활용해서 이 부드러워지는 현상을 해결하려고 했다. 그 결과 시각적으로 만족스러운 결과를 낼 수 있게 되었다. 하지만 SRGAN의 결과는 여전히 실제 HR 이미지와는 거리가 있었다. ESRGAN은 SRGAN을 보완하기 위해 다음과 같은 세 가지 측면에서 모델을 개선했다.

1. 네트워크 구조 개선(RDDB, 배치정규화 제거, Residual scaling, smaller initialization)

2. Discriminator에 상대적인 realistic을 평가하는 Relativistic average GAN(RaGAN) 구조 사용

3. VGG loss를 구할 때, 활성화 함수를 통과하기 전의 값을 사용하기

 

네트워크 구조

위 그림은 ESRGAN 논문에 실린 SRGAN의 residual block과 ESRGAN의 RRDB 구조를 비교하는 그림이다. SRGAN의 경우는 Residual Block을 여러개 연결하고 residual connection을 사용했다면, RRDB는 Dense Block을 연결하고 residual scaling을 사용했다. Dense Block의 경우 convolution layer와 Leaky ReLU로 이루어진 레이어들 간에 Dense connection을 사용한 블럭이다. Dense connection은 이전 레이어의 output을 현 레이어의 input과 concat해서 같이 레이어를 통과시키는 방법인데, residual connection이 elementwise sum을 사용한다면 Dens connection은 두 텐서를 concat한다는 점이 다르다. 코드를 보면 이해가 더 쉽다.

class DenseResidualBlock(nn.Module):
    """
    The core module of paper: (Residual Dense Network for Image Super-Resolution, CVPR 18)
    """

    def __init__(self, filters, res_scale=0.2):
        super(DenseResidualBlock, self).__init__()
        self.res_scale = res_scale

        def block(in_features, non_linearity=True):
            layers = [nn.Conv2d(in_features, filters, 3, 1, 1, bias=True)]
            if non_linearity:
                layers += [nn.LeakyReLU()]
            return nn.Sequential(*layers)

        self.b1 = block(in_features=1 * filters)
        self.b2 = block(in_features=2 * filters)
        self.b3 = block(in_features=3 * filters)
        self.b4 = block(in_features=4 * filters)
        self.b5 = block(in_features=5 * filters, non_linearity=False)
        self.blocks = [self.b1, self.b2, self.b3, self.b4, self.b5]

    def forward(self, x):
        inputs = x
        for block in self.blocks:
            out = block(inputs)
            inputs = torch.cat([inputs, out], 1)
        return out.mul(self.res_scale) + x

위 코드는 Dense Block을 nn.Module을 사용해 구현한 코드인데, forward 부분을 보면 torchc.cat을 통해 input과 output을 concat하고 다음 레이어에 input으로 들어가는 구조를 볼 수 있다. 이런 방식으로 가면 input layer의 채널이 점점 늘어나게 되지만 마지막 레이어의 output의 채널 수는 filters 만큼이 되기 때문에 최종 output의 채널 수는 보존된다.

그리고 return 부분을 보면 residual scaling이 사용된 것을 볼 수 있다. 일반적인 residual connection이라면 out + x를 리턴하겠지만 res_scale 인자를 받아 out에 곱해줌으로써 residual connection을 전부 연결하지 않고 스케일링하여 연결해주었다.

이 Dense Block을 연결해 최종적으로 만들어지는 RRDB Block은 논문의 그림처럼 3개의 Dense Block을 reidual scailing을 사용해 연결했다. 코드는 다음과 같다.

class ResidualInResidualDenseBlock(nn.Module):
    def __init__(self, filters, res_scale=0.2):
        super(ResidualInResidualDenseBlock, self).__init__()
        self.res_scale = res_scale
        self.dense_blocks = nn.Sequential(
            DenseResidualBlock(filters), DenseResidualBlock(filters), DenseResidualBlock(filters)
        )

    def forward(self, x):
        return self.dense_blocks(x).mul(self.res_scale) + x


class GeneratorRRDB(nn.Module):
    def __init__(self, channels, filters=64, num_res_blocks=16, num_upsample=2):
        super(GeneratorRRDB, self).__init__()

        # First layer
        self.conv1 = nn.Conv2d(channels, filters, kernel_size=3, stride=1, padding=1)
        # Residual blocks
        self.res_blocks = nn.Sequential(*[ResidualInResidualDenseBlock(filters) for _ in range(num_res_blocks)])
        # Second conv layer post residual blocks
        self.conv2 = nn.Conv2d(filters, filters, kernel_size=3, stride=1, padding=1)
        # Upsampling layers
        upsample_layers = []
        for _ in range(num_upsample):
            upsample_layers += [
                nn.Conv2d(filters, filters * 4, kernel_size=3, stride=1, padding=1),
                nn.LeakyReLU(),
                nn.PixelShuffle(upscale_factor=2),
            ]
        self.upsampling = nn.Sequential(*upsample_layers)
        # Final output block
        self.conv3 = nn.Sequential(
            nn.Conv2d(filters, filters, kernel_size=3, stride=1, padding=1),
            nn.LeakyReLU(),
            nn.Conv2d(filters, channels, kernel_size=3, stride=1, padding=1),
        )

    def forward(self, x):
        out1 = self.conv1(x)
        out = self.res_blocks(out1)
        out2 = self.conv2(out)
        out = torch.add(out1, out2)
        out = self.upsampling(out)
        out = self.conv3(out)
        return out

RRDB 블럭의 코드와 Generator 전체의 코드다. Generator의 코드의 RRDB 블럭 이외의 부분은 SRGAN과 구조가 동일해보인다. Generator 쪽에서는 RRDB를 사용하여 모델을 더 깊게 만들고 학습을 용이하게 만들었다는 점이 가장 큰 변화인 것 같다.

Relativistic Discriminator(RaGAN)

ESRGAN의 Discriminator는 최종 결과물 자체를 통해서만 해당 이미지가 real인지 fake인지 구별하지 않는다. RaGAN의 아이디어를 사용하여 생성된 이미지의 피쳐맵과 진짜 이미지의 피쳐맵의 평균과 차이를 구한 뒤, 그 값이 real인지 fake인지를 구별한다. 논문에 삽입된 수식은 다음과 같다.

이 그림은 일반적인 GAN과 RaGAN의 Discriminator가 어떻게 다른지 보여준다. RaGAN에서 사용하는 평균은 모든 미니배치의 real, fake 이미지 피쳐맵을 합쳐 평균낸 것이다. 이것을 코드로 구현하면 다음과 같다.

criterion_GAN = torch.nn.BCEWithLogitsLoss().to(device)

pred_real = discriminator(imgs_hr).detach()
pred_fake = discriminator(gen_hr)

loss_GAN = criterion_GAN(pred_fake - pred_real.mean(0, keepdim=True), valid)

원문 코드에서 RaGAN 부분만 발췌하여 가져온 것이다. 일반적인 GAN이었다면 loss가 Criterion_GAN(pred_fake, valid)와 같았겠지만 RaGAN 구조를 사용하기 위해 실제 이미지를 Discriminator에 통과시켜서 평균을 구해 빼주고 있다. 이 loss은 Discriminator를 업데이트할 때도 사용되고, 위 코드처럼 Generator의 GAN loss로도 사용된다.

VGG loss

ESRGAN은 활성화 함수를 통과하기 전의 피쳐맵 값을 이용해서 VGG loss를 구한다. 이전의 SRGAN은 활성화 함수를 통과한 후의 피쳐맵을 이용했는데, 이러면 피쳐맵이 sparse 해진다는 단점이 있다. 그 결과로 업데이트가 제대로 이루어지지 않는다. 활성화 함수를 통과한 피쳐맵과 통과하기 전의 피쳐맵의 차이는 다음과 같다.

이 사진에서 윗쪽은 활성화 함수를 통과하기 전, 아래쪽은 활성화 함수를 통과한 후의 피쳐맵인데 한 눈에 봐도 활성화 함수를 통과하고 난 피쳐맵은 비활성화된 피쳐들이 많다. 이런 현상은 더 깊은 네트워크의 피쳐맵일 수록 심해진다. 위 사진에서 왼쪽과 오른쪽의 차이다. 오른쪽 아래를 보면 거의 모든 피쳐들이 비활성화되어 색이 보이는 피쳐가 많지않다. ESRGAN은 활성화 함수를 통과하기 전의 피쳐맵을 사용하여 이런 문제점을 해결하고자 했다.

Network Interpolation

기본적으로 GAN은 학습이 불안정하기 때문에 super resolution의 결과물에도 noise가 끼어 있을 수도 있다. 반면 이전의 PSNR 최적화 방식으로 학습된 네트워크의 경우 MSE의 영향으로 noise가 없이 부드러운 모습이지만 디테일은 떨어지는데, Network Interpolation은 이 두 딥러닝 네트워크의 weight를 합쳐 서로의 장점을 취하겠다는 것이다.

Network Interpolation은 위와 같이, 패러미터 alpha에 따라 PSNR로 학습한 네트워크와 GAN으로 학습한 네트워크의 패러미터들을 합성하는 것이다. 두 네트워크의 결과물의 값을 합성하는 것과 generator loss의 패러미터를 조절하는 것보다 network interpolation이 더 효과적으로 노이즈를 제거해줬다고 한다.

학습

최종적으로 Generator와 Discriminator의 학습과정은 다음과 같다. 우선 PSNR 최적화 방식으로 Generator를 먼저 학습시킨다. 이는 네트워크가 local optima에 빠지는 것을 방지하고 Discriminator가 완전 랜덤한 이미지를 받지 않도록 해준다. 그 후 Generator와 Discriminator을 수렴할 때까지 번갈아 학습시킨다. 모델 학습의 최종적인 코드는 다음과 같다.

enerator = GeneratorRRDB(opt.channels, filters=64, num_res_blocks=opt.residual_blocks).to(device)
discriminator = Discriminator(input_shape=(opt.channels, *hr_shape)).to(device)
feature_extractor = FeatureExtractor().to(device)

# Set feature extractor to inference mode
feature_extractor.eval()

# Losses
criterion_GAN = torch.nn.BCEWithLogitsLoss().to(device)
criterion_content = torch.nn.L1Loss().to(device)
criterion_pixel = torch.nn.L1Loss().to(device)

# Optimizers
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))

Tensor = torch.cuda.FloatTensor if torch.cuda.is_available() else torch.Tensor


# ----------
#  Training
# ----------

for epoch in range(opt.epoch, opt.n_epochs):
    for i, imgs in enumerate(dataloader):

        batches_done = epoch * len(dataloader) + i

        # Configure model input
        imgs_lr = Variable(imgs["lr"].type(Tensor))
        imgs_hr = Variable(imgs["hr"].type(Tensor))

        # Adversarial ground truths
        valid = Variable(Tensor(np.ones((imgs_lr.size(0), *discriminator.output_shape))), requires_grad=False)
        fake = Variable(Tensor(np.zeros((imgs_lr.size(0), *discriminator.output_shape))), requires_grad=False)

        # ------------------
        #  Train Generators
        # ------------------

        optimizer_G.zero_grad()

        # Generate a high resolution image from low resolution input
        gen_hr = generator(imgs_lr)

        # Measure pixel-wise loss against ground truth
        loss_pixel = criterion_pixel(gen_hr, imgs_hr)

        if batches_done < opt.warmup_batches:
            # 처음엔 PSNR로만 학습
            loss_pixel.backward()
            optimizer_G.step()
            print(
                "[Epoch %d/%d] [Batch %d/%d] [G pixel: %f]"
                % (epoch, opt.n_epochs, i, len(dataloader), loss_pixel.item())
            )
            continue

        # Extract validity predictions from discriminator
        pred_real = discriminator(imgs_hr).detach()
        pred_fake = discriminator(gen_hr)

        # Adversarial loss (relativistic average GAN)
        loss_GAN = criterion_GAN(pred_fake - pred_real.mean(0, keepdim=True), valid)

        # Content loss
        gen_features = feature_extractor(gen_hr)
        real_features = feature_extractor(imgs_hr).detach()
        loss_content = criterion_content(gen_features, real_features)

        # 세 loss를 패러미터를 이용해 합침
        loss_G = loss_content + opt.lambda_adv * loss_GAN + opt.lambda_pixel * loss_pixel

        loss_G.backward()
        optimizer_G.step()

        # ---------------------
        #  Train Discriminator
        # ---------------------

        optimizer_D.zero_grad()

        pred_real = discriminator(imgs_hr)
        pred_fake = discriminator(gen_hr.detach())

        # Adversarial loss for real and fake images (relativistic average GAN)
        loss_real = criterion_GAN(pred_real - pred_fake.mean(0, keepdim=True), valid)
        loss_fake = criterion_GAN(pred_fake - pred_real.mean(0, keepdim=True), fake)

        # Total loss
        loss_D = (loss_real + loss_fake) / 2

        loss_D.backward()
        optimizer_D.step()

원본 코드에서 모델 학습 부분만 발췌했다. 코드를 보면 warmup_batches가 되기 전까지는 L1loss로만 학습하고 그 후에 Generator와 Discriminator를 같이 학습하는 모습을 볼 수 있다. Generator의 loss는 GAN loss(Adversarial loss - RaGAN), Content loss(VGG loss), Pixel loss(L1 loss)가 패러미터를 이용해 조합되어 만들어진다.

결과

이 논문은 논문에서 제시한 개선점들을 하나씩 제거해가며 결과물의 차이를 보면서, 개선점들이 유의미했는지 증명하려 했다. 그 결과 개선점들이 모두 이미지의 디테일을 올리는 데 도움을 주고 있다는 것을 알 수 있었다. 또한 RRDB 구조를 사용하고 배치 정규화를 없앰으로써 모델은 더 깊으면서 학습은 쉽게 만들 수 있었다. 

또한 network interpolation은 0에서 1 사이의 0.2 간격으로 패러미터를 주어 실험되었는데, 그 결과 network interpolation이 이미지의 부드러운 정도와 이미지 디테일 퀄리티 사이의 밸런스를 컨트롤 할 수 있었다

  • 주의: 이 포스팅은 제가 읽고 이해한대로 작성한 논문리뷰이며 잘못된 지식이나 오역이 있을 수 있습니다.

서론

  • language modeling이나 기계번역에서 가장 최신의 접근 방법은 LSTM, GRU, RNN 등이었다.

  • 이 모델들은 recurrent 구조를 이용해서 이전의 데이터를 기억해 다음 학습에 반영한다는 특징이 있다.

  • 그러나 이 구조는 기본적으로 모델의 병렬화를 불가능하게 해서 시퀀스가 너무 길 때나 메모리의 제한이 있을 때 문제가 생길 수 있다.

  • 한편 어텐션 메커니즘은 시퀀스의 길이에 상관없이 시퀀스간의 의존도를 모델링하는 데 필수적인 메커니즘이 되었다.

  • 이 논문에서 우리는 Transformer라는 recurrnece를 제거하고 대신에 오직 어텐션 메커니즘만 사용하여 global dependencies를 반영하는 모델 구조를 제안한다.

  • Transformer는 병렬화에 수월하고 기계번역의 새로운 최신 모델이 될 수 있다.

본론

  • Transformer 모델은 Encoder(왼쪽)과 Decoder(오른쪽)으로 이루어져있다.

  • Transformer 모델은 단어 임베딩에 positional embedding 값을 더해준다. Transformer는 recurrence 구조를 사용하지 않기 때문에 시퀀스의 position 정보를 따로 임베딩하는 것이 필요하다. positional embedding은 단어 임베딩과 같은 차원을 가지고 있다.이 논문에서 positional embedding은 sin, cos와 같은 주기함수를 이용하여 값을 매핑해주었다.

  • Encoder는 multi-head self-attention 레이어와 fully connected layer로 이루어져있다. 또한 잔차연결을 사용한다. 잔차 연결을 위해 임베딩 레이어를 포함한 모든 레이어에서 output의 차원은 512이다.

  • Decoder는 Masked multi-head self-attention 레이어를 사용한다.이 레이어는 예측단어 보다 앞에 나오는 단어만 사용하여 어텐션을 진행한다.(미래 참조를 막기 위해) 그 다음 Encoder의 output을 이용해 함께 multi-head attention을 진행한다.

self-head attention이란?

  • self-head attention은 query를 key, value 쌍에 매핑을 하는 레이어이다. 여기서 query는 시퀀스 중 하나의 단어이고, key는 그 외에 모든 단어이다. value는 해당 key에 해당하는 값을 말한다.
  • 어텐션은 다른 단어들이 해당 단어에 얼마나 영향을 미치는지 계산하여 weight를 매핑하는 레이어이다.
  • multi head attention은 마지막에 weight를 softmax로 내보내고 이 값과 value를 matmul을 진행하여 최종 output을 출력한다.

결론

  • 이 논문에서 우리는 오직 어텐션만을 사용한 첫번째 transduction 모델을 제안했다. 이 모델은 인코더 디코더 구조의 모델에서 가장 흔히 쓰이는 recurrent layer를 대체했다.

  • 기계번역 task에서 Transformer는 recurrent layer나 convolution layer를 사용한 아키텍쳐보다 상당히 빠른 속도로 학습될 수 있다. WMT 2014 영어-독어 번역, 영어-불어 번역 task에서 다른 모델들과 그 모델들의 앙상블 모델에 비해 가장 좋은 성능을 보였다.

  • 우리는 Transformer 모델을 번역 task를 넘어서 이미지, 음성, 영상 등의 큰 input과 output을 다루는 문제에까지 확장할 예정이다.

출처 링크: https://arxiv.org/abs/1609.04802
주의: 제가 읽고 이해한대로 작성한 논문 리뷰입니다. 오역이나 학문적으로 틀린 내용이 있을 수 있습니다

SRGAN

서론

  • super resolution 문제는 유일한 하나의 정답이 없는 불량조건문제(ill-posed problem)이다.
  • 기존에는 SR을 위해 pixel-wise MSE를 줄이고 PSNR을 최대화 하는 방식으로 진행됐다.
  • 그러나 이것들은 경계선을 과하게 부드럽게 만들고 평가 계수가 개선되더라도 실질적으로는 성능이 나아지지 않았다.
  • 따라서 본 논문에서는 기존 방식과는 다른 loss function을 제시하고, GAN을 사용한 SR을 제안한다.

본론

GAN 구조

  • SRGAN은 generator와 discriminator로 구성된다.
  • generator는 잔차연결과 PReLU를 사용한 residual block 5개, pixel suffler을 사용한 block 두개로 이루어져 있다.
  • discriminator는 conv layer와 batch normalization, Leaky ReLU를 조합한 블럭 7개로 이루어져 있다.

perceptual loss function

  • perceptual loss function은 content loss와 adversarial loss를 결합한 loss function이다.
  • content loss는 VGG loss를 사용한다. VGG loss는 VGG network에서 추출된 특성맵의 유클리드 거리를 구하는 방식이다.
  • adversarial loss는 generator가 생성한 super resolution 이미지를 discriminator가 진짜 이미지로 분류할 확률에 log를 취하여 합한 것이다. 즉 discriminator가 분류를 잘 못할 수록 adversarial loss가 줄어든다.

SRGAN 설계

  • VGG 특성맵은 MSE와 비슷한 스케일이 되도록 1/12.75로 스케일링 되었다.

  • optimization 방법으로 adam이 선택되었고 베타값은 0.9이다. 학습률은 0.00001로 설정했다. 또한 100000번의 학습 iteration을 거쳤다.

  • test 과정에서는 batch normalization 블록을 사용하지 않았다.

결론

  • MSE를 loss로 사용하면 PSNR은 최대화할 수 있지만 adversarial loss와 결합되더라도 VGG loss를 사용하는 것에 비해 인지적인 최적화 수준은 낮다.
  • VGG54를 사용한 SRGAN이 기존 SRResNet에 비해 통계적으로 유의미하게 더 성능이 좋았다.
  • 더 깊은 layer의 VGG를 사용한 VGG loss를 사용할 경우 질감의 디테일이 더 좋아지는 경향을 확인할 수 있었다.
  • SRGAN이 다른 모든 super resolution 네트워크의 성능을 큰 격차로 이겼다.

Discussion

  • 본 논문의 SRGAN은 시간적으로 최적화된 모델이 아니므로 실시간 동영상의 SR에는 적합하지 않다. 그러나 연구에 따르면 더 얕은 layer를 사용하면 성능은 비슷한 반면에 훨씬 더 높은 시간효율성을 보여준다.
  • ResNet 디자인이 더 깊은 network에 큰 효과를 가져왔다고 추정한다.
  • SRGAN의 layer가 깊을수록 매우 훈련하기 어려워진다는 사실도 알아냈다.
  • VGG54을 사용한 VGG loss가 좀 더 사실적인 super resolution 이미지를 만들 수 있다는 사실을 알았다.

주의: 이 글은 작성자가 이해한대로 쓴 글이며 학문적으로 틀린 사실이 존재할 수 있습니다

확률이란 무엇인가

확률이라는 단어는 일상생활에서 흔히 마주치는 단어다. 네이버 국어사전은 일정한 조건 아래에서 어떤 사건이나 사상(事象)이 일어날 가능성의 정도. 또는 그런 수치라고 정의하고 있다. 그렇다면 어떤 사건의 가능성을 어떻게 수치로 나타낸단 말인가. 초중고 교육과정을 거친 사람이라면 어떤 사건이 일어날 경우의 수를 일어날 수 있는 모든 사건의 경우의 수로 나누는 방법으로 확률을 구할 수 있다는 사실을 알 것이다. 그러나 현실은 그렇게 간단히 확률이 구해지지 않는다. 앞에서 소개한 방법은 일어날 수 있는 모든 사건이 독립적이라는 가정하에 정의된 확률이다. 현실에선 여러가지 사건이 얽혀 전에 일어났던 사건이 미래의 사건에 영향을 미칠 수 있는 경우가 많다. 또한 일어날 수 있는 모든 사건의 경우의 수를 구하는 것도 어렵다. 당장 어떤 학생이 대학에 합격할 확률을 구한다고 할 때, 일어날 수 있는 모든 경우의 수란 무엇인가. 공부를 열심히 하지 않을 경우, 공부를 했는데 시험장에 지각할 경우, 1차 모집에선 불합격했으나 추가 합격을 할 경우 등등 어떤 경우의 수라고 부르기도 애매한 사건들이 가득하다.

확률을 바라보는 다양한 시각

확률론에선 이러한 애매한 경우들을 모두 뒤로하고 확률을 수치로 표현할 수 밖에 없다. 그렇다면 어떻게 확률을 수치로 표현할 수 있는가? 이에 대해서 확률을 바라보는 두가지 관점이 있다. 빈도주의 관점과 베이지안 관점이 있다. 빈도주의 관점의 경우, 단어 그대로 확률을 어떤 사건이 일어나는 빈도로 생각한다. 10만명의 학생이 시험을 쳐서 그 중 5만명의 학생이 대학에 합격했다면, 빈도주의 관점에선 대학에 합격할 확률은 2분의 1이다. 빈도주의는 이 시험과 얽힌 다양한 사건과 불확실성을 고려하기보단 그저 빈도에만 관심이 있다. 베이지안 관점은 빈도주의 관점보다 좀 복잡하다. 베이지안 관점은 확률을 여러가지 사건이 얽힌 불확실성으로 표현한다. 여러 얽힌 사건들을 조건부 확률로 표현한다. 베이지안 관점에서 확률을 구하는 일반적인 방법은 관측된 데이터를 기반으로 사전확률과 우도를 구하고, 그것을 이용해 사후확률을 구하는 방법이다.

빈도주의 관점과 베이지안 관점의 차이

빈도주의 관점의 경우 확률을 구할 때 어떤 사건이 일어나는 빈도만 세면 되기 때문에 간편하다. 그러나 일어나는 빈도가 매우 적은 사건이라면 어떤가. 아니면 아직 일어나지도 않은 사건이라면? 지구의 빙하가 모두 녹을 확률은 어떨까. 빈도주의 관점에선 이 확률은 0이다. 아직 지구의 빙하가 모두 녹은적도 없기 때문이다. 그러나 지구온난화가 계속 진행중이라는 것은 사실이며 언젠가 빙하가 모두 녹을 가능성이 없다고 장담할 수 없다. 빈도주의는 이처럼 매우 드물게 일어나는 사건이나 아직 일어나지 않은 사건의 확률을 구하려고 할 때 왜곡된 확률을 도출할 가능성이 있다.

반면 베이지안 관점의 경우 빈도주의 관점에서 잡아내지 못하는 불확실성을 고려하여 확률을 표현할 수 있다. 베이지안 관점에서는 지구의 빙하가 모두 녹을 확률이 정말 낮은 확률이긴 하겠지만 확실히 0은 아닐 것이다. 베이지안 관점은 지금까지 관측된 데이터를 토대로 불확실한 확률을 추측할 수 있다.

하이퍼파라미터 튜닝

딥러닝이나 머신러닝 컴피티션을 나간다면 반드시 필요한 과정이 하이퍼파라미터 튜닝이다. 대부분의 머신러닝, 딥러닝 모델들은 별도의 하이퍼 파라미터 튜닝없이도 괜찮은 성능을 내긴하지만, 리더보드에서 상위권에 들기 위해서는 그것만으로는 부족하다. 리더보드 상위권의 경쟁은 매우 치열한데 결국 그 조금의 점수를 올리기 위해 하이퍼파라미터 튜닝을 시도하게 된다.
일반적으로 하이퍼파라미터를 튜닝하는 방법으로, scikit-learn의 그리드서치나 랜덤서치 객체를 사용하는 것이 유명하다. 하지만 이 방법들은 매우 고달픈 방법이다. 최적의 하이퍼파라미터를 찾는 과정이 효율적이지 않을 뿐더러 값을 계속 바꿔가면서 입력해줘야하는 것이 불편하다. 모델의 하이퍼파라미터를 튜닝하는 방법은 그리드서치, 랜덤서치 이외에도 더 좋은 방법이 있다. 그리고 이런 방법들을 쉽게 사용할 수 있는 라이브러리들이 존재한다. 이 포스팅에선 그 라이브러리들의 종류와 사용방법에 대해서 써보겠다.

Hyperopt

hyperopt는 베이지안 최적화 알고리즘을 사용하는 것을 도와주는 라이브러리다. 베이지안 최적화는 그리드서치나 랜덤서치보다 하이퍼파라미터를 튜닝하는 과정을 최적화시키는 알고리즘이다. hyperopt에서 베이지안 최적화는 TPE(Tree of parzen estimator)라는 것을 사용한다. TPE는 하이퍼파라미터 별로 모델을 실험하면서 손실함수의 값을 기록한다. 그리고 그 손실함수의 값이 일정한 threshold를 넘은 것과 넘지 못한 것을 분리한다. 그 후 threshold를 넘은 하이퍼파라미터의 확률분포를 구하여 최적 파라미터를 찾아가는 알고리즘이다.
hyperopt는 하이퍼파라미터가 들어갈 수 있는 space를 생성하고, 손실함수를 정의한 뒤, fmin 메소드를 통해 손실함수를 최소화하는 최적 하이퍼파라미터를 찾는 과정으로 이루어져 있다. 그리고 최적화 과정을 기록하기 위해서 따로 Trial 객체를 사용할 수도 있다. 예제 코드는 다음과 같다.

# 손실함수(목적함수) 정의
def objective(args):
    case, val = args
    if case == 'case 1':
        return val
    else:
        return val ** 2

# space 정의
from hyperopt import hp
space = hp.choice('a',
    [
        ('case 1', 1 + hp.lognormal('c1', 0, 1)),
        ('case 2', hp.uniform('c2', -10, 10))
    ])

# 손실함수를 minimize하는 값 찾기
from hyperopt import fmin, tpe
best = fmin(objective, space, algo=tpe.suggest, max_evals=100)

print best
# -> {'a': 1, 'c2': 0.01420615366247227}
print hyperopt.space_eval(space, best)
# -> ('case 2', 0.01420615366247227}

# 출처: 공식문서

hyperopt의 경우 lightgbm같은 앙상블 모델에 적용하기 적합하다. 그러나 공식문서를 참고해보니 최근에는 잘 쓰이지 않는다는 언급이 있어 이 점을 참고해야할 듯 하다.

Optuna

Optuna도 사용법은 hyperopt와 비슷하다. 그러나 hyperopt보다 관리가 잘 되고 있는 것으로 보이고 머신러닝 모델 뿐만 아니라, 텐서플로, 케라스, 파이토치 등으로 작성한 딥러닝 모델에도 적용할 수 있다고 공식문서에 쓰여 있다. 또한 최신 알고리즘과 병렬화를 지원한다. 가급적 hyperopt보다 optuna를 사용하는 것이 좋을 것으로 보인다. optuna도 hyperopt와 마찬가지로 목적함수와 space를 정의하고 optimize 메소드를 통해 최적 하이퍼파라미터를 탐색한다. 최적화 알고리즘은 공식문서에 따르면 hyperopt와 마찬가지로 TPE 알고리즘을 사용하고 있다. 공식문서의 예제코드는 다음과 같다.

import optuna

def objective(trial):
    x = trial.suggest_float('x', -10, 10)
    return (x - 2) ** 2

study = optuna.create_study()
study.optimize(objective, n_trials=100)

study.best_params  # E.g. {'x': 2.002108042}

Kerastunner

케라스튜너는 텐서플로와 케라스로 작성한 딥러닝 모델의 하이퍼파라미터 최적화 라이브러리다. 케라스튜너는 베이지안최적화와 더불어 hyperband 알고리즘을 지원한다. 케라스튜너는 케라스 자체에서 지원하는 라이브러리이므로, 관리도 잘 될 것이라고 예상할 수 있다. 다만 다른 머신러닝 모델이나 파이토치로 작성한 딥러닝 모델에는 사용하기 어렵다는 것이 단점이다.
케라스 튜너의 경우는 모델을 정의하는 model_builder 함수를 정의해줘야 한다. model_builder 함수는 input으로는 하이퍼파라미터를 받고 모델 객체를 리턴한다. 그리고 하이퍼밴드나 베이지안최적화와 같은 알고리즘을 선택하고 최적화를 진행한다. 예제코드는 다음과 같다.

def model_builder(hp):
  model = keras.Sequential()
  model.add(keras.layers.Flatten(input_shape=(28, 28)))

  # Tune the number of units in the first Dense layer
  # Choose an optimal value between 32-512
  hp_units = hp.Int('units', min_value = 32, max_value = 512, step = 32)
  model.add(keras.layers.Dense(units = hp_units, activation = 'relu'))
  model.add(keras.layers.Dense(10))

  # Tune the learning rate for the optimizer 
  # Choose an optimal value from 0.01, 0.001, or 0.0001
  hp_learning_rate = hp.Choice('learning_rate', values = [1e-2, 1e-3, 1e-4]) 

  model.compile(optimizer = keras.optimizers.Adam(learning_rate = hp_learning_rate),
                loss = keras.losses.SparseCategoricalCrossentropy(from_logits = True), 
                metrics = ['accuracy'])

  return model

  tuner = kt.Hyperband(model_builder,
                     objective = 'val_accuracy', 
                     max_epochs = 10,
                     factor = 3,
                     directory = 'my_dir',
                     project_name = 'intro_to_kt')

  tuner.search(img_train, label_train, epochs = 10, validation_data = (img_test, label_test), callbacks = [ClearTrainingOutput()])

# Get the optimal hyperparameters
best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0]

print(f"""
The hyperparameter search is complete. The optimal number of units in the first densely-connected
layer is {best_hps.get('units')} and the optimal learning rate for the optimizer
is {best_hps.get('learning_rate')}.
""")

예제코드로 볼때, 케라스 튜너는 위 두 라이브러리보다 딥러닝 모델을 최적화할 때 더 용이한 특성을 가지고 있는 것으로 보인다. 또한 목적함수를 model_builder와 분리함으로써, 목적함수를 바꿔가며 하이퍼파라미터를 찾을 때도 더 간편할 것으로 예상된다. 또한 하이퍼밴드라는 강력한 알고리즘을 제공하는 것도 케라스 튜너의 장점이다.

+ Recent posts