올 한해 동안 테스트용으로 여러개의 Django 용 EC2 인스턴스를 만들면서 했던 삽질들 정리

작성할 내용의 99%는 구글링과 ChatGPT에서 얻은 내용이므로 컴플라이언스 이슈 없습니다

 

1. 기본 구조

데이터엔지니어링을 위한 테스트용 웹서버를 구축하는 간단한 구조입니다

- AWS EC2 서비스로 생성한 인스턴스를 사용합니다

- Django app의 view.py 에서 간단한 기능을 수행합니다 (주로 HTTP 요청 처리)

- NGINX 와 Gunicorn 으로 웹서버를 구축합니다

- AWS의 Route53와 Application Load Balancer 를 이용해서 HTTPS 인증서와 서브도메인을 할당합니다

 

 

2. EC2 인스턴스에 패키지 설치

 

2.1. Ubuntu + NGINX + Django + Gunicorn 설치

EC2 인스턴스를 생성할 때 OS를 선택할 수 있는데, 저는 기본적으로 Ubuntu LTS 를 선호합니다.

LTS(Long Term Support)는 Ubuntu 버전중에서 안정적인 버전이므로 특정기능을 테스트할 때 편리하게 사용할 수 있습니다

현재는 22.04 버전이 가장 최신버전입니다.

오래된 소스코드 및 라이브러리에 의존성이 있는 특수한 경우가 아니라면, 지나치게 오래된 버전을 고집하는 것도 지양합시다

 

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install python3-pip nginx

pip install django gunicorn

Ubuntu 에는 버전에 맞는 파이썬이 설치되어있지만, 그 외 패키지들은 직접 설치해주어야합니다

pip 와 nginx 를 apt 로 설치해주고, Django, Gunicorn 를 pip 로 설치해줍니다.

단일 테스트용 EC2 서버에서는 가상환경을 설정할 필요가 없으므로 가상환경 설정은 생략합니다.

 

### Django Project
django-admin startproject my_project .

### Install test
python ~/my_project/manage.py runserver 0.0.0.0:8000

Django 프로젝트 생성에 대한 자세한 설명은 생략합니다

오류메시지가 없다면 설치가 잘 되었겠지만~

그냥 넘어가면 허전하니까 Django 설치를 확인하는 runserver 0.0.0.0:8000 를 테스트로 실행해줍시다!

브라우저의 localhost:8000 에서 Django 의 웰컴메시지를 볼 수 있습니다 :D

 

이 때, 아래와 같이 django-admin 을 찾을 수 없다는 에러가 발생한다면 PATH 설정이 되어있지 않은 상태입니다

>> WARNING: The script django-admin is installed in '/home/ubuntu/.local/bin' which is not on PATH.

 

PATH 설정을 하기위한 간단한 방법입니다

## SHELL 확인 
echo $SHELL 
>> /bin/bash 

## 편집 열기
sudo nano ~/.bashrc 

## 경로추가 - 다음의 내용을 추가해주세요
export PATH="$HOME/.local/bin:$PATH"

## reload 
source ~/.bashrc

먼저 SHELL 을 확인하기 위한 간단한 코드를 실행하면 bash 라고 응답을 받습니다

bashrc 파일을 열고 Django가 설치된 /bin 디렉토리를 PATH 에 추가합니다

 

 

 

2.2. Gunicorn 설정

### Edit gunicorn configure
sudo nano /etc/systemd/system/gunicorn.service

### 아래의 내용을 적어줍니다
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/workspace
ExecStart=/home/ubuntu/.local/bin/gunicorn --workers 3 --bind unix:/home/ubuntu/workspace/my_project/my_project.sock my_project.wsgi:application

[Install]
WantedBy=multi-user.target

### Gunicorn 실행
sudo systemctl daemon-reload
sudo systemctl enable gunicorn.service
sudo systemctl start gunicorn.service

### 상태 확인
systemctl status gunicorn

gunicorn.service 에 위의 내용을 적어서 저장합니다

EC2 우분투 인스턴스에서 유저는 'ubuntu' 이므로, User = ubuntu 로 설정해줍니다.

worker 갯수는 적당히 3으로 설정하고, gunicorn 이 실행될 때 생기는 socket 위치를 설정합니다.

모든 경로는 각자의 개발환경에 맞게 잘 수정해주어야 오류가 나지않습니다.

start gunicorn.service 를 실행하면 설정된 경로에 .sock 파일이 생깁니다

 

2.3. NGINX 설정

### Edit configure
sudo nano /etc/nginx/sites-available/my_project

### 아래의 내용을 적어줍니다
server {
    listen 80;
    server_name {서버 주소};

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/ubuntu/workspace/my_project;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/ubuntu/workspace/my_project/my_project.sock;
    }
}

server_name 에는 웹서버 주소를 적어줍니다. IP주소 형식일 수도 있고, 서브도메인 주소일 수도 있겠죠.

Gunicorn 설정에서 만든 .sock 파일과 경로가 일치하는지 다시 확인해줍니다.

 

그리고 일반적인 EC2 환경에서 502 Permission Error 가 생기므로 반드시 수정해주어야 할 부분이 있습니다.

connect() to (생략)/my_project.sock failed (13: Permission denied) while connecting to upstream,

### 권한을 가진 유저 확인하기
ls -la workspace/my_project/my_project.sock
>> srwxrwxrwx 1 ubuntu www-data 0 Mar  2 07:50 workspace/my_project/my_project.sock

### Edit configure
sudo nano /etc/nginx/nginx.conf

### User 를 www-data 에서 ubuntu 로 바꾸어줍니다

nginx.conf 파일에 기본 유저가 www-data 로 설정되어있지만

.sock 파일에 접근 권한을 가진 EC2 루트유저의 이름은 ubuntu 이므로 권한 오류가 발생합니다

이것 때문에 꽤나 고생했네요

 

 

2.4. Django 테스트

장고에대해 쓰는 글은 아니지만 설정확인을 위한 간단한 방법을 만듭니다

### app 만들기
django-admin startapp my_app

각자의 개발환경에서 적절하게 디렉터리를 맞추고 startapp 으로 앱을 만들어줍니다

 

개발 메모에서 실제 프로그램 구조를 지우면서 글을 작성하다보니 urls.py 설정은 생략합니다.

 

ALLOWED_HOSTS=['*']

ALLOWED HOST 에러가 뜰 것이니 settings.py 에 모든 호스트를 허락해줍니다

from django.http import HttpResponse

def hello(request):
    return HttpResponse('hello world this is test')

view.py 에 테스트용 코드를 적어주고나서 HTTP 요청을 보내면 'hello workd this is test'라는 응답을 받습니다

 

 

2.5. (선택) Redis 로 캐시 저장하기

만약 Django 캐시를 사용하는 상황이라면,

위에서 Gunicorn worker 를 3으로 설정하다보니 각각의 worker가 다른 캐시를 가지고 있게 됩니다.

즉 웹사이트를 새로고침할때마다 다른 캐시 결과를 보여주는 문제가 생깁니다.

이 문제를 풀 간단한 해결방법으로 Redis 를 사용합니다

 

sudo apt-get install redis-server

pip install django-redis


### settings.py 에 다음의 내용을 추가
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",  # Adjust as needed
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

###

 

redis-server 와 django-redis 를 설치한 뒤에 장고의 settings.py 에 캐시에 대한 내용을 추가로 작성해주면

간단하게 모든 Gunicorn worker 가 동일한 캐시를 가집니다!

빅데이터 처리에서 파이썬 API를 이용하는 pyspark 를 배워보고자합니다

가볍게 로컬에서 pyspark를 연습하기 위해서 WSL 기반의 연습환경을 구축합니다.

 

 

(1) 윈도우에 WSL2설치 

Powershell을 관리자 권한으로 실행하고 wsl을 설치합니다

wsl --install

>> 시스템을 다시 부팅할 필요가 없습니다.

위와 같은 오류메시지와 함꼐 실행되지 않을 경우에는 아래처럼 wsl 사용을 활성화합니다.

이 단계에서 여러 유형의 오류가 발생할 수 있는데 마소의 WSL 공식매뉴얼을 보는 것도 좋습니다. 

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

 

wsl을 설치하고 우분투를 설치합니다.

현재는 버전 정보를 입력하지 않을 경우 Ubuntu 20.04(LTS)가 default로 설치됩니다.

wsl --install --d Ubuntu

 

그리고 ubuntu를 실행하면 다음과 같은 메시지가 뜹니다.

Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.10.16.3-microsoft-standard-WSL2 x86_64)

첫 실행에서는 UNIX user 계정과 비밀번호를 설정해야합니다.

sudo 명령어를 입력할때 비밀번호를 입력해야하므로 기록해둬야합니다.

 

 

(2) 파이썬 설치

실제 개발할 때, 개발환경 구축에 대한 연습을 하기 위해서

conda보다는 직접 가상환경을 만들고 모듈을 설치해볼 계획입니다.

 

먼저 pyenv 를 설치합니다.

이 때 .bashrc 파일에 pyenv에 관련한 PATH를 추가해줍니다.

$ curl https://pyenv.run | bash
## 경고메시지출력
## WARNING: seems you still have not added 'pyenv' to the load path.

--------- .bashrc 파일에 아래 내용 추가 -------

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv virtualenv-init -)"

--------------------------------------

## 쉘 재실행
$ exec "$SHELL"

## 설치확인
$ pyenv --version

 

그리고 파이썬을 설치하기전에,

파이썬, pyspark 등 모듈에 필요한 기본 dependency를 설치해줍니다.

 

그 뒤에 pyenv를 이용해서 파이썬을 설치해줍니다.

저는 최신버전인 3.10.4를 설치했습니다.

 

버전이나 개발환경에 따라 여러가지 에러가 뜰 수 있는데

그때마다 에러메시지를 구글링하면 어떤 모듈을 설치해야하는지 쉽게 알 수 있습니다.

https://github.com/pyenv/pyenv/wiki/Common-build-problems

설치한 뒤에 해결이 안된다면 파이썬을 재설치 해줍니다.

 

아래는 메모삼아 정리해둔건데 다시보니 중복되는 것도 있네요. 이 부분은 개인환경에 따라 잘 설치하면 됩니다.

## 파이썬에 필요한 모듈 설치 
sudo apt-get install build-essential
sudo apt-get install libedit-dev libffi-dev libbz2-dev
sudo apt-get install autoconf automake autopoint autotools-dev blt-dev debhelper dh-autoreconf
  dh-strip-nondeterminism diffstat docutils-common gettext intltool-debian
  libarchive-zip-perl libbluetooth-dev libbluetooth3 libbz2-dev libcroco3 libdb-dev
  libdb5.3-dev libexpat1-dev libffi-dev libfile-stripnondeterminism-perl libfontconfig1-dev
  libfreetype6-dev libgdbm-dev libice-dev libjs-jquery libjs-sphinxdoc libjs-underscore
  liblzma-dev libmpdec-dev libncursesw5-dev libpixman-1-0 libpng-dev libpthread-stubs0-dev
  libreadline-dev libsm-dev libsqlite3-dev libssl-dev libtcl8.6 libtext-unidecode-perl
  libtimedate-perl libtinfo-dev libtk8.6 libtool libx11-dev libxau-dev libxcb1-dev
  libxdmcp-dev libxext-dev libxfont2 libxft-dev libxkbfile1 libxml-libxml-perl
  libxml-namespacesupport-perl libxml-sax-base-perl libxml-sax-perl libxrender-dev
  libxss-dev libxss1 libxt-dev m4 pkg-config po-debconf python-babel-localedata
  python3-alabaster python3-babel python3-docutils python3-imagesize python3-lib2to3
  python3-pygments python3-roman python3-sphinx python3-tz quilt sgml-base sharutils
  sphinx-common tcl tcl-dev tcl8.6 tcl8.6-dev tex-common texinfo tk tk-dev tk8.6
  tk8.6-blt2.5 tk8.6-dev x11-xkb-utils x11proto-core-dev x11proto-dev
  x11proto-scrnsaver-dev x11proto-xext-dev xml-core xorg-sgml-doctools xserver-common
  xtrans-dev xvfb zlib1g-dev
  
sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev
  
pyenv install 3.10.4

pyenv versions

 

다음으로 pyenv로 가상환경을 생성합니다.

저는 venv_spark 라는 이름으로 가상환경을 생성했습니다.

vscode는 이미 설치해뒀고 윈도우용 설치에 어려움이 없으므로 자세한 방법은 생략합니다.

## 가상환경 생성
pyenv virtualenv 3.10.4 venv_spark

## 현재 환경을 "venv_spark"로 설정
pyenv local venv_spark

## vscode 실행
code .

 

(3) spark 설치

spark는 java 기반으로 개발되었으므로 먼저 openjdk 를 설치해줍니다.

그리고 https://spark.apache.org/downloads.html 에서 이용하려는 버전의 링크를 복사해서 wget으로 입력해줍니다.

다운받은 뒤에는 tar 명령어로 압축해제를 해줍니다.

여기서는 spark버전 3.2.1으로 입력했습니다.

$ sudo apt-get install openjdk-8-jrea

$ wget {https://dlcdn.apache.org/spark/spark-3.2.1/spark-3.2.1-bin-hadoop3.2.tgz}
$ tar spark-3.2.1-bin-hadoop3.2.tgz

 

그리고 spark 또한 PATH에 추가해줘야겠지요

vi 편집기를 이용해서 아래의 내용을 입력해줍니다

{spark_folder}는 위에서 압축해제된 폴더경로를 입력해주고

{python_folder}에 which python 으로 출력되는 파이썬 설치경로를 입력해줍니다.

$ sudo vi /etc/profile.d/spark.sh

--------리눅스 vi 편집기 입력 -----------

export SPARK_HOME={spark_folder}
export PATH=$SPARK_HOME/bin:$PATH
export PYSPARK={python_folder}

----------------------------------------

# 환경설정을 적용합니다
$ . /etc/profile

 

우분투에서 pyspark 를 열어서 설치를 확인해봅니다.

여기서 dependency 오류가 발생하면 계속 검색하면서 추가모듈을 설치해야합니다 

$ pyspark

 

웰컴메시지가 뜨면 성공적으로 설치가 되었음을 확인합니다

 

(4) 파이썬에서 import pyspark 확인

 

하지만 파이썬 터미널에서 import pyspark 를 실행하면,

pyspark 와 py4j 모듈 위치를 못찾아서 오류가 발생했습니다.

 

.bashrc 파일을 열어서 아래처럼 경로를 추가로 적어줍니다

export SPARK_HOME=/home/hyungmin/spark-3.2.1-bin-hadoop3.2
export PYTHONPATH=$SPARK_HOME/python:$SPARK_HOME/python/build:$PYTHONPATH
export PYTHONPATH=$SPARK_HOME/python/lib/py4j-0.10.9.3-src.zip:$PYTHONPATH

그러고나면 드디어 파이썬에서 

import pyspark 가 오류없이 실행됩니다 !!

 

 

 

[참고 블로그]

https://livebook.manning.com/book/data-analysis-with-python-and-pyspark/a-installing-pyspark-locally/v-1/5

 

A Installing PySpark locally · Data Analysis with Python and PySpark

 

livebook.manning.com

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=semtul79&logNo=221489908486 

 

how to install apache spark on ubuntu ( 우분투에서 spark 설치 )

ubuntu 18 에서 apache spark 을 설치해서 구동해 봅시다. ( 일단 python 으로 프로그래밍하는 전제로 환경...

blog.naver.com

https://smothly.github.io/develop%20environment/linux/2020/03/07/윈도우에-우분투(wsl)-파이썬-개발환경-구축하기.html 

 

윈도우에 우분투(wsl) 파이썬 개발환경 구축하기 | Seungho's Blog

윈도우에 우분투(wsl) 파이썬 개발환경 구축하기 최근에 우분투를 지우고 다시 윈도우로 돌아왔습니다. 윈도우에서 개발환경을 맞추려고 하니 환경변수 설정이나 터미널 환경 등이 불편해서 윈

smothly.github.io

 

머신러닝 튜토리얼에서 모델을 만들 때는 코드 내에서 각종 변수를 직접 입력하면서 테스트하지만,

실제 모델을 이용할 때는 각종 옵션을 argument로 정의해서 CLI 스크립트에서 입력할 수 있어야 한다

 

여기서부터는 머신러닝 라이브러리가 아닌 파이썬 프로그래밍의 영역이라 좀 낯설지만 한번 공부해봅시다!

 

 

 

파이썬에 내장된 argparse 라이브러리를 이용해야한다.

라이브러리 공부는 공식문서가 깔끔하니까 쭉 정리해보자 ( https://docs.python.org/3/library/argparse.html  )

The argparse module makes it easy to write user-friendly command-line interfaces. 
The program defines what arguments it requires, and argparse will figure out how to parse those out of sys.argv.

서두에 말한 것과 같이 argparse 모듈을 이용하면 CLI 에서 머신러닝의 모델, 데이터, 환경 등등 여러 옵션과 파라미터를 설정할 수 있다. argparse는 입력된 스크립트에서 어떻게 argument 를 파싱할지 정의할 수 있는 모듈이다.

 

여기서 sys.argv란 파이썬 스크립트에 입력되는 argument 덩어리들이라고 생각하면 된다

sys.argv : The list of command line arguments passed to a Python script.

 

 

이제 간단한 예제로 실습을 해보자

#arg_test.py

import argparse

# (1) 인스턴스 생성
parser = argparse.ArgumentParser(description='parser for argparse test')

# (2) argument 추가
parser.add_argument('--number', default=int(0), type=int, help='number to print')

# (3) parse 결과 
args = parser.parse_args()

print('your number is {}'.format(args.number))

(1)  ArgumentParser() 를 이용해서 parser 라는 인스턴스를 생성한다.

parser는 파이썬 스크립트로 입력된 값들을 저장하고 처리할 수 있는 인스턴스이다

description 으로 인스턴스 설명을 추가해도 되고 생략해도 된다.

The ArgumentParser object will hold all the information necessary to parse the command line into Python data types.

 

 

(2) add_argument() 메서드로  파싱할 argument 정의를 추가한다.

메서드 파라미터들이 다양한데, 머신러닝할 때는 위의 5개 정도를 쓰지않을까 생각해서 강조해봤다.

name or flags - Either a name or a list of option strings, e.g. foo or -f, --foo.
object.type - The type to which the command-line argument should be converted.
dest  - The name of the attribute to be added to the object returned by parse_args().
default  - The value produced if the argument is absent from the command line and if it is absent from the namespace
help - A brief description of what the argument does.
action - The basic type of action to be taken when this argument is encountered at the command line.
nargs - The number of command-line arguments that should be consumed.
const - A constant value required by some action and nargs selections.
choices - A container of the allowable values for the argument.
required - Whether or not the command-line option may be omitted (optionals only).
metavar - A name for the argument in usage messages.

 

 

 

(3) parse_args() 메소드로 파싱한 결과로 args 라는 인스턴스를 생성한다

parser.parse_args() 로 스크립트로 입력된 값들을 파싱한다.

그 결과 생성된 Namespace 오브젝트를 args 라는 이름으로 정의하여 저장하면 된다

parse_args() method will inspect the command line, convert each argument to the appropriate type and then invoke the appropriate action.
In most cases, this means a simple Namespace object will be built up from attributes parsed out of the command line:

 

Namespace 는 별거아니고 그냥 parse_args 결과로 생성되는 클래스이다

Namespace : Simple class used by default by parse_args()  to create an object holding attributes and return it.

 

 

이제 커맨드 창을 열어서 테스트 해보자!

.py 파일을 실행시키면 default 값인 0으로 출력되지만

.py 뒤에 --number 10 을 적어주면 arg.number 이 10이라는 뜻으로 파싱하고, 결과로 10을 출력해준다.

오케이 간단한 테스트는 성공

 

 

 

 

그럼 이제 머신러닝 모델에서 쓸법한 argparse 코드구조를 생각해보자.

(실제로 구동되는 코드는 아니며 argparse의 사용되는 부분만 작성합니다)

# __main__.py
import os
import argparse
import subprocess

parser = argparse.ArgumentParser()

parser.add_argument('--model', default='run_my_model', type=str)
parser.add_argument('--data', default='my_data', type=str)
parser.add_argument('--n_epoch', default=int(100), type=int)

args = parser.parse_args()

subprocess.run(["python" , 
                os.path.join('.',arg.model,'.py'),
                "--data",
                args.data,
                "--n_epoch",
                args.n_epoch
                ])

모델연산하는 run_my_model.py파일을 바로 실행시킬 수도 있지만, __main__은 여러 다른 기능을 포함할 수 있으니

__main__에서 subprocess 를 이용해서 모델실행 코드를 실행시키도록 만들 수 있다.

 

__main__.py 파일을 실행하면서 스크립트에 입력된 argument를 파싱해서 args로 저장하고,

이 값을 subprocess 스크립트로 my_model.py 를 실행시킬 때 다시 argument로 넘겨준다

 

 

 

 

# run_my_model.py

import argparser
import os
import tensorflow as tf

# argument parse
parser = argparse.ArgumentParser()
parser.add_argument('--n-epoch',default=int(100),type =int)
parser.add_argument('--data', default = "my_data")
args = parser.argparser()



# 모델 생성 예시
model = Model_Dev()

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
              loss=tf.keras.losses.BinaryCrossentropy(),
              metrics=[tf.keras.metrics.BinaryAccuracy(),
                       tf.keras.metrics.FalseNegatives()])

model.compile(optimizer='adam', loss='mse')
model.fit(os.listdir("datasource/" + args.data + ".csv"),
          epochs=args.n_epochs)

이렇게 학습에 필요한 데이터경로와, 학습 하이퍼파라미터인 n_epoch 등 을

argument로 파싱해서 적용할 수 있다.

 

 

연구단계에서 여러 하이퍼파라미터의 학습결과를 비교해보고 싶다거나

online으로 운영중인 모델의 경우 데이터 경로가 새로 생성될 때 , 또는 cross-validation 할 때

위의 방법처럼 커맨드에서 argument로 입력해서 전체 프로그램을 돌릴 수 있다.

 

 

 

 

[참고한 블로그]

https://greeksharifa.github.io/references/2019/02/12/argparse-usage/

 

Python, Machine & Deep Learning

Python, Machine Learning & Deep Learning

greeksharifa.github.io

 

결정트리 기반의 알고리즘 중 캐글 등에서 현재까지도 가장 널리 이용되는 알고리즘인 XGBoost에 대해 알아보자

XGBoost는 Tianqi Chen 과 Carlos Guestrin 에 의해 2014년 경 개발되어 캐글 커뮤니티 등에서 널리 알려진 후 ,

KDD2016에서 XGBoost에 대한 논문을 발표했다. (멋지다..😎)

 

T.Chen 의 슬라이드XGBoost 논문을 기초로 주요 내용을 정리해보았다.

사실 2016년 논문은 이미 SOTA급 성능을 보여주는 알고리즘에 대해서 '자세히 설명해줄게' 하는 논문이라,

라이브러리 사용에 필요한 것 이상으로 어려운 내용을 담고 있긴하다. (나만 어려운거 아니겠지??)

 

그래도 오늘 날잡고 수식까지 뽀개본다는 마음가짐으로 한번 읽어보자 !!

 

 

 

(1) 모델의 기본 개념

모델의 기본 수식 (Chen, 2016)

K개의 결정트리를 이용하는 모델이라면 다음과 같이 prediction 값과 objective function 이 정의된다

 

이 때, loss term을 최적화할수록 학습 정확도는 높아지지만 오버피팅의 위험이 생긴다.

 

regularization term은 모델의 variance를 낮추어 오버피팅을 방지하는 대신 학습데이터에서 정확도는 약간 희생한다. regularization 이 측정하는 모델의 complexity는 다음과 같이 정의할 수 있다.

Regularziation term 의 정의와 예시

 

즉, regularization term 은 "too many split" 을 방지하는 역할을 해준다

정리하자면 XGBoost의 objective term은 예측정확도와 모델복잡도를 균형있게 최적화하는 목표함수라 할 수 있다.

 

 

 

(2) 최적화 목표 정의하기

 

XGBoost 는 Boosting ( =Additive) 알고리즘을 기초로 한다

자세한 수식을 보자.

 

① y(t) = y(t-1) + f_t(x) 

② obj(t) = SIGMA{ l(y , y(t)) + regularization } 

①을 ② 에 대입하면,

③ obj(t) = SIGMA { l(y, y(t-1) + f_t(x) } + regularization_t

 

그러면 우리는 t번째 반복에서 다음의 총 LOSS 를 가장 적게 만드는 f_t 를 찾는 것으로 학습목표를 정의할 수 있다.

"let ˆy (t) i be the prediction of the i-th instance at the t-th iteration, we will need to add f_t to minimize the following objective."

 

 

그 다음으로는 f_t를 first order gradient인 g, second order gradient인 h로 나타내어

테일러 전개를 실행하면 목적함수 최적화에 좋다고 한다 (사실 이 부분은 이해가 잘안되지만 그렇다고 치자 ㅎㅎ;;)

"Second-order approximation can be used to quickly optimize the objective in the general setting"

 

여기서 y_i와 yhat(t-1)의 예측오차는 t번째에서 최적화할 수 없는 상수항이므로 제거하면

simplifed objective at step t 을 다음과 같이 정의할 수 있다

 

 

 

그리고 처음에 정의했던 complexity (=regularization) term 을 오메가 대신 대입하고,

instance set of leaf j 에 대해서 정리한다. 

수식의 전개는 그렇게 어렵지 않은데 변수가 많아지니까 혼란스럽다.

sigma(i=1~n) [ g_i ] 가 sigma(j=1~T) [ sigma(i∈I_j) g_i ] 로 바뀌는 부분, h_i에서도 동일하게 바뀌고 

sigma(i=1~n) [ f_t(x_i) ] 에서 sigma(j=1~T) [ w_j ] 로 바뀌는 부분에 유의하면 된다.

(이쯤되면 그냥 수식을 읽어보고 따라갈 수만 있어도 괜찮지않을까.. ㅠ)

 

 

 

이제 optimal weight in each leaf(j) 값과, 최적화된 objective 값을 정의해보자.

 

일단 구조가 동일하다고 가정하므로 gamma*T 항과 sigma(j=1~T) 등을 무시하고

w에 대해서 미분하면 아래와 같이 optimal weight W* 를 정의할 수 있다.

(g와 h의 시그마 값을 편의상 G와 H 로 표기)

그림판 퀄리티 죄송합니다 ㅎㅎ;;

 

그리고 w_j 자리에 W* 를 대입하고 원래 수식을 정리하면 된다!

즉 마지막 식의  G^2 / (H+λ) 항이 "how good a tree structure is"를 평가하는 부분이다

 

 

chen의 슬라이드에서 structure score calculation 부분을 보며 다시 정리해보자

 

 

 

 

(3) Greedy Learning 방법 

위에서 우리는 각 트리구조를 평가하는 식에 대해 정의했다.

하지만 가능한 모든 트리구조를 만들고 평가해서 최적의 구조를 결정할 수는 없으니,

greedy한 방법으로 구조를 학습하는 방법을 만들어야 한다.

 

 

각 노드가 스플릿될 때, Gain은 위와 같이 정의한다.

(어디서 나온 수식인지는 모르겠지만 논문에서도 특별히 설명이 없으니까 그냥 넘어가자...)

 

XGBoost는 빠르고 효율적인 연산을 위해서 최적의 split을 근사하는 방법을 이용한다.

Algorithm 1은 가능한 모든 split을 계산하는 'exact greedy algorithm' 이며

Algorithm 2는 데이터의 피처별 분포를 고려해서 split proposal을 생성하는 'approximate algorithm' 이다

 

global proposal은 처음에 모든 트리 구조 후보를 propose하는 방법이고,

local proposal은 각 split 이후에 다시 propose 하는 방식이다.

논문의 Figure 3. 를 보면 적절히 후보생성만 된다면 성능에 큰 차이는 없어보인다.

 

 

 

그리고 pre-pruning 과 post-pruning 에 대한 내용은 논문에는 없지만 슬라이드에 간략히 소개되어 있다.

보통 pruning 조건 같은 하이퍼파라미터를 정하는 규칙은 한가지 정답이 없고 휴리스틱에 의존한다.

XGBoost의 경우 Gain 식에서 'training loss reduction' 항이 'regularization'항보다 적을때 Gain이 음수가 된다는 점에서 힌트를 얻는다.

즉 loss reduction 이 충분히 작게 일어나고 있으므로 트리를 더 만들 필요가 없다고 판단하는 기준으로 이용한다.

 

 

 

 

(4) Sparsity 를 다루는 방법

실제 데이터셋을 다루는 엔지니어라면 대부분 데이터의 sparsity 에 관련한 고민을 한다.

XGBoost 는 'default direction' 이라는 방식으로 missing value, frequent zeros , one-hot-encoding 등 sparse한 데이터를 다룬다.

 

default direction 을 만드는 방식은 Algorithm 3. 와 같다.

간단하게 이해하면 missing value가 없는 데이터셋에서 최적의 dafult 옵션을 학습하는 방식이다.

특히 이 sparsity-aware 알고리즘은 sparse한 데이터에 대해 연산속도를 빠르게 만드는 효과도 있다고 한다.

 

 

 

 

 

 

이후 논문에 있는 내용들은 XGBoost 의 작동원리에 크게 중요하지 않고, 예측 성능과 컴퓨팅 효율에 대한 내용이다.

 

- 3.2. 에서 오버피팅을 막기 위한 방법으로 Shrinkage and Column subsampling 를 설명

- 3.3. 과 Appendix 에서 split point 후보를 만드는 방법으로 weighted quantile sketch를 제안

- 4. 에서 데이터를 메모리에서 다루는 block 생성 방법

 

이 내용은 XGBoost의 기본 원리를 알아보는 이번 포스팅에서도 다루지 않겠다.

저자의 슬라이드에도 생략되어있고, 라이브러리를 잘적용하면 보통 사용자는 신경쓸 일이 거의 없을 것이다 😁

 

 

중간에 수식이 좀 많았지만,

수학적 엄밀함만 살짝 패스하면 알고리즘의 기본 원리는 어렵지않게 이해할 수 있었다.

그럼 XGBoost는 여기까지 !

 

 

+) 이해에 도움을 준 한국어 블로그

https://soobarkbar.tistory.com/32

 

XGBoost: A Scalable Tree Boosting System

논문 http://dmlc.cs.washington.edu/data/pdf/XGBoostArxiv.pdf Abstract 부스팅 트리 (Boosting Tree) 는 매우 효과적이고 널리 사용되는 머신러닝 방법임. 이 논문에서 우리는 XGBoost 라는 확장 가능한 (Scal..

soobarkbar.tistory.com

https://brunch.co.kr/@snobberys/137

 

XGBoost 사용하기

지루하고, 재미없기 짝이 없지만 꾸준한 조회수를 보장할 것 같은 글 | 소개 시작은 캐글(kaggle)이었다. 캐글이 무엇인지 처음 읽는 분들을 위해서 잠깐 설명하자면, <캐글>은 과학자들이 통계적

brunch.co.kr

 

decision tree 기반의 머신러닝 알고리즘은 딥러닝이 등장한 이후에도 여전히 tabular 데이터에 대해서는 최적의 효과를 보여주고 있다.

XGBoost, LightGBM, CatBoost 등 알고리즘의 기반이 되는 트리 알고리즘의 기초를 정리해보자.

 

(1) 결정 트리의 구조

 

Iris 데이터를 이용한 결정트리 예시 ( 출처: Grabusts et al., 2015)

 

결정트리 알고리즘은 위의 그림처럼 데이터셋의 속성(변수)의 분류기준을 만드는 방식이다.

여기서 각 분류의 마지막 부분(초록색박스)는 leaf 라고 부른다.

각 분기점마다 어떤 기준을 세우는지, 몇번의 분기점을 만들 것인지에 따라 결정트리의 정확도가 달라질 수 있다.

 

 

출처: https://tensorflow.blog/파이썬-머신러닝/2-3-5-결정-트리/

트리의 형성과 데이터셋의 분포를 그림으로 나타낸 좋은 그림을 찾았다.

각각의 결정트리 분기점은 데이터셋의 평면에서 구획을 잘게 쪼개면서 분류 정확도를 높이는 것을 목표로 한다.

 

하지만 만약 너무 큰 트리를 만들어서, 하나의 구획에 하나의 데이터포인트가 있다면?

학습 데이터셋에서 분류정확도는 100%처럼 보이겠지만, 결정트리 모델이 데이터셋에 오버피팅(과적합)되었기때문에

테스트 성능은 매우 낮을 것이다.

 

오버피팅을 막기위해서는 사전에 트리의 최대 깊이를 제한하는 pre-pruning 방식과 트리를 만들고 난 후 데이터포인트가 적은 노드를 합치는 post-pruning 방식을 이용할 수 있다.

결정트리 알고리즘을 라이브러리에서 구현할 때, max_depth 를 파라미터로 입력하는 것이 pre-pruning에 해당한다.

 

(2) 앙상블(Ensemble) 로 더 나은 모델 만들기

 

결정트리 알고리즘은 간단해서 좋지만 그만큼 언더피팅 or 오버피팅의 위험이 많아보인다.

그리고 데이터셋과 변수가 복잡해질수록 상위트리의 형성에 따라 모델의 정확도가 크게 바뀔 것 같기도하다.

 

출처: Tianqi Chen, presentation on XGBOOST

여러개의 결정트리 모델을 앙상블하여 분류 또는 회귀모델을 만드는 것으로 이런 문제를 해결할 수 있다.

특별히 결정트리(Decistion Tree)를 앙상블 한 모델 즉 Tree Ensemble 모델은 Forest 라고도 부른다.

 

이 때 모델 앙상블 방법은 크게 Bagging 과 Boosting으로 나눌 수 있다.

출처: https://towardsdatascience.com/ensemble-learning-bagging-boosting-3098079e5422

 

Bagging은 Bootstrap Aggregation 의 약자로

데이터셋을 임의복원추출(=bootstrap)하여 여러개의 결정트리 모델을 만들고,

각각의 결정트리에서 계산된 가중치를 합산(aggregation)하여 최종적으로 분류 또는 회귀 결과를 만드는 방법이다.

RandomForest 가 bagging을 이용하는 대표적인 방법이다.

 

Boosting은 '예측이 틀린 부분에 더 집중해서 학습'하는 순차적(sequential)인 모델 앙상블 방식이다.

대표적인 AdaBoost 알고리즘을 보면, 

처음에는 모든 데이터포인트에 동일한 가중치를 주고 임의로 데이터 샘플을 추출하여 결정트리 모델을 만든다.

그 다음, 예측모델의 오차를 반영하여 잘못 예측한 데이터포인트의 가중치를 높이고 다음 데이터 샘플을 추출한다.

이 과정을 통해 다음 샘플링에서는 이전 단계에서 잘못 예측한 데이터가 포함될 가능성이 높아지고,

모델은 이전모델에서 잘못 예측한 부분에 더욱 성능을 개선하도록 유도된다.

 

 

 

Bagging 은 모두 임의추출된 데이터로 만든 모델들을 앙상블하여 오버피팅의 위험을 줄일 수 있다.

하지만 어려운 task일수록 최종 모델의 예측 정확도를 높이기 어렵고,

정확도를 높이기위해 결정트리의 수를 늘리는만큼 연산량이 증가한다.

 

Boosting 은 정확도면에서 좋은 결과를 보여주는 덕분에

대부분의 tabular 데이터에 대해 가장 적용하기 좋은 알고리즘들이 Boosting 방식을 이용한다.

반면, 가중치 부여와 학습 방식에 따라 정확도와 학습속도 등 성능이 달라질 수 있다.

 

다음 글에서 결정트리 기반으로 현재까지 가장 임팩트 있는 모델들(XGBoost, LightGBM 등)에 대해서

좀더 자세히 알아보도록 하겠다

 

 

+ Recent posts