심심해서 하는 블로그 :: '파이썬' 태그의 글 목록

'파이썬'에 해당되는 글 3건

캐글에서 사랑받는 XGBoost

요즘 캐글에서 문제를 도전해보는 재미로 공부를 하고 있습니다.

최근에는 Tabular Playground Series - Jan 2021[ kaggle.com/c/tabular-playground-series-jan-2021 ] 를 푸는 중에 다른 사람들이 올려놓은 노트북을 참고 해보니,

대부분이 XGBoost를 사용해서 문제를 풀고 있는 것을 확인 했습니다.

왜 이렇게 사랑 받고 있는지 궁금해서 직접 사용해보고 느낀 점을 정리해서 포스팅 해보곘습니다.


앙상블 기법 : Boosting

그 전에 XGBoost에서 사용하는 Boosting이라는 앙상블 기법에 대해서 간단하게 설명을 해보겠습니다.

앙상블은 직관적으로 "집단 지성" 이라고 생각을 하면 편리합니다. 여러 개의 모델을 사용해서 각각의 예측 결과를 만들고 그 예측 결과를 기반으로 최종 예측결과를 정하는 방법이죠


그 중 Boosting이란 방법은 비교적 약한 모델들을 결합하여 하나의 강한 모델 만드는 과정입니다.

모델 A이 예측한 결과를 다음 모델인 B에서 보완하여 예측하고 최종적으로 C에서 보완을 해서 예측 결과를 만들어 내는 방식이죠.

Boosting을 사용하는 대표적인 모델은 AdaBoost, Gradient Boost 등등이 이 있고 XGBoost는 그 중 Gradient Boosting 을 사용해서 모델링을 합니다.


Gradient Boosting 설명을 수학없이 간결하게 해준 유튜브 영상이 있어서 링크를 남겨 둘게요.

이거보다 설명 잘 할 수 없을거 같아요. 꼭 보세요. 두 번 보세요.

https://www.youtube.com/watch?v=3CC4N4z3GJc


XGBoost : Extreme Gradient Boosting

장점 : 계산 속도 향상, Kaggle에서 수차례 우승한 경력

sklearn 에도 Gradient Boosting 로 Regression 또는 Classification 을 할 수 있는 매소드를 제공하고 있습니다만, 시간이 너무 오래 걸립니다.

하지만 XGBoost의 경우에는 병렬 연산을 지원하여 계산 속도를 향상 시킵니다.

또한 GPU를 사용 할 수 있는 옵션을 제공하기 때문에 학습하는데 소요되는 시간을 절약할 수 있습니다.

이것은 하이퍼파라미터 튜닝을 할 때도 특히 빛이 납니다. (CPU 만으로 50회를 돌리다 하루가 넘게 소요되서 당황스러웠던 기억이 있습니다.)

 

XGBoost는 Overfitting 을 Overfitting 을 제어 하기 위해 두 가지 방법을 제안합니다.

1. 모델의 복잡도를 제어하는 방법 : max_depth, min_child_weight, gamma 하이퍼파라미터 사용

2. 다음 모델로 Loss 를 전달할 때 Random Value를 추가하는 방법 : subsample, colsample_bytree 파라미터 사용

 

그 외에도 early_stopping_rounds 를 사용해서 눈에 띄지 않게 Loss가 감소하지 않은 경우에는 학습을 진행하지 않도록 제어도 가능합니다.

학습 성능의 경우에는 kaggle의 수차례 우승한 사례를 봤을 때, 충분히 우수하다고 판단됩니다.


 

 

단점 : Tree Based Learning, 복잡한 하이퍼 파라미터

 

모든 학습 알고리즘이 그렇듯, 장점과 단점이 있기 때문에 데이터의 분포나 상황에 알맞게 사용해야 합니다.

 

1. Tree Based Learning 를 학습할 때 학습 데이터에 예민하게 반응합니다.

학습 데이터에서 1~10 사이의 범주를 가진 라벨로 학습을 진행한 모델이 있다고 합시다.

이 모델은 데이터의 예측을 1 ~ 10 사이의 값으로 예측을 하고자 하고 10 초과, 1 미만으로 반환을 하기 어렵습니다.

 

2. 적절하게 튜닝을 하지 않은 모델은 Overfit이 쉽게 발생하는 문제 때문에 반드시 진행해야하는 절차입니다.

그런데 XGBoost는 튜닝을 할 때 손봐야할 파라미터가 너무 많습니다.

(당장 xgboost 공식 홈페이지에 가서 내용을 봐도, 파라미터가의 수가 너무 많습니다.)


소스코드

https://www.kaggle.com/ssooni/xgboost-lgbm-optuna

소스코드에는 XGBoost 외에도 LightGBM, Optuna, Stacking 앙상블을 사용한 내용도 있습니다.


,

1. pyplot 시작하기

python에서 데이터 가시화를 위한 라이브러리로 matplotlib.pyplot을 사용한다. 파이썬이 데이터를 처리하는 데에는 일가견이 있는 언어인데 가시화 라이브러리도 있다니.. (R에만 있는 줄 알았는데)

일단 저는 윈도우에서 PyCharm을 설치해서 사용하기 때문에 윈도우에서 설치하는 과정만 소개하겠습니다.



위의 URL에서 사용중인 파이썬의 버전에 맞춰서 다운받은 다음 실행하시면 됩니다.


2. 그래프 그리기 : plot()

간단한 직선 그리기

그래프를 그릴 때 필요한 것은 X축과 Y축, 데이터겠죠?? 

일단 처음 그려보는거니까 간단한 걸로 한번 그려 봅시다.


1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt
 
# y좌표 데이터만 있는 경우
plt.plot([123])
# (x,y) 좌표를 둘 다 사용할 경우
plt.plot([1,2,3],[2,4,6])
# axis([x축 시작, x축 끝, y축 시작, y축 끝])
plt.axis([0,4,0,7])
plt.show()
cs


첫 번째 그린 그래프가 파란색 선이에요. Y좌표만 입력받았을 경우에는 축의 시작점 부터 1씩 증가하는 꼴로

좌표가 자동으로 매칭되요. plot([1,2,3])의 경우에는, 따라서, (0,1), (1,2), (2,3)을 이은 선이 그려지는 거죠.


두 번째 주황색 그래프는 (X, Y) 좌표를 직접 입력한 경우에요. 앞의 리스트에는 X축의 좌표, 

뒤에는 Y축의 좌표로 사용해요. 따라서 plot([1,2,3],[2,4,6])은 (1,2), (2,4), (3,6)을 이은 을 그리게 되죠.


axis는 화면에 보여줄 축의 최대 값과 최소 값을 지정하는 함수로 주석에서 보셨듯이, 

이 그래프의 X축은 0~4 까지 Y축은 0 ~ 7까지로  설정된 것을 그래프를 통해서도 확인이 가능할 거에요.

그리고 그래프를 화면에 보여주는 show()함수를 사용하는 것으로 마무리!!!



이쁘게 그리고 싶어요..

plot에는 다양한 옵션들이 있어요. 그래프 색깔을 지정할 수 있는 옵션과 선의 스타일도 지정하는 옵션도 있어요. 저는 그 중에서 몇가지 종류만 사용해 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
# y = x^2 그래프
plt.plot(x, x**2'r--')
# y = 2^x 그래프
plt.plot(x, 2**x, 'b')
plt.axis([0,6,0,40])
plt.show()
cs



 r

빨강 

 --

점선 

 b

파랑 

 o

동그라미 

g

초록 

^

삼각형 

y

노랑 

 *

별 


이 정도가 제가 알고 있는 스타일과 색상이에요. pyplot의 공식페이지에서 더욱 다양한 옵션들이 있어요.


하나의 창 여러 개 그래프

그래프를 이 때까지는 하나의 축상에서 두 개씩 그리는 것을 연습했어요. 그런데 여러 개의 그래프를 각각의 창으로 표현하는 방법도 있어요. 바로 subplot()을 이용하는 방법이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
# y = x^2 그래프
plt.subplot(221)
plt.plot(x, x**2'r--')
# y = 2^x 그래프
plt.subplot(222)
plt.plot(x, 2**x, 'b-')
 
# y = x
plt.subplot(223)
plt.plot(x, x, 'g')
 
# y = 1/(x+1)
plt.subplot(224)
plt.plot(x, 1/(x+1))
 
plt.show()
cs



subplot()에 들어가는 숫자에 대하여 해설을 하자면 221은 전체창을 2 * 2로 나눈 뒤 1번째 그래프를 의미해요. 222는 2 * 2 중 2번째 이런 식으로 말이죠. 분할하고자 하는 화면의 갯수에 따라서 숫자를 다르게 쓰면 됩니다.


그래프를 상세하게 표기하기

여러 개의 그래프를 subplot으로 그리는 경우나 하나의 축에 그래프를 그리는 경우 제목이나 범례가 없으면 어떤 그래프인지 알기 힘들 겁니다. title() 함수와 legend() 함수를 이용해서 그래프의 제목과 범례를 지정할 수 있답니다. 그리고 X축과 Y축의 의미도 있으면 좋겠죠? X축과 Y축에 라벨을 붙여주는 xlabel(), ylabel()함수도 한 번 사용해보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
 
# 그래프의 타이틀 설정
plt.title('two graph')
plt.plot(x, x, 'g', label='y=x')
plt.plot(x, 2 * (x+1), 'r', label='y=2*(x+1)')
 
# X, Y축에 이름을 붙어주기
plt.xlabel('X Data')
plt.ylabel('Y Data')
 
# 범례 표시
plt.legend()
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs



3. Scatter 사용하기

산포도 그래프를 그리고 싶다!

분류기를 사용하여 데이터를 분류하는 경우 산포도 그래프로 데이터를 가시화하는 것을 많이 볼 수 있습니다. 데이터가 어느 쪽에 밀집해 있는지를 확인하기 쉽기 때문인데요. matplotlib.pyplot의 scatter() 함수를 이용하면 산포도 그래프를 손쉽게 그릴 수 있답니다. 예제 코드를 보시죠!



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt
import numpy as np
 

x1 = [12112069]
y1 = [3710201630]
 
x2 = [571692]
y2 = [381023432]
 
# 그래프의 타이틀 설정
plt.title('two scatter')
plt.scatter(x1, y1, marker='s', c='r')
plt.scatter(x2, y2, marker='*', c='b')
# X, Y축에 이름을 붙어주기
plt.xlabel('X Data')
plt.ylabel('Y Data')
 
# 범례 표시
plt.legend()
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs


두 개의 데이터를 간단하게 준비하고 scatter() 함수를 사용해서 그래프를 그립니다. 속성 중에 marker 속성이 있는데 이 속성을 이용하면 네모, 세모, 별 모양 등의 모양을 지정할 수 있고, c는 색깔 속성을 의미합니다. 그 외 다양한 속성들은 pyplot.scatter() 페이지에서 확인할 수 있습니다!


4. annotation 이용하기

아니 이런 기능도 있어??

단순히 그래프 그리는 용도로 선 그래프나 산포도 그래프 등의 그래프를 그리는 함수만 있는 게 아니다. 그래프에 주석을 달거나 화살표를 그리는 등의 기능도 있다.  이 기능을 사용해서 트리형 그래프를 그릴 수도 있고 표시가 필요한 부분을 표시하는 등의 기능을 수행할 수 있습니다. 예제 코드를 한 번 볼까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt
import numpy as np
 
x1 = [12112069]
y1 = [3710201630]
 
# box 속성
bboxType = dict(boxstyle="round4", alpha=0.8, fc='w')
# 화살표 속성
arrow = dict(arrowstyle='-|>', connectionstyle='arc3,rad=0.2', fc="w")
# 그래프의 타이틀 설정
plt.title('annotation')
plt.scatter(x1, y1, marker='*', c='r', s=40 )
 
# xy는 도착점, xytext는 text가 위치할 장소
plt.annotate('here!!', xy=(11,10), xytext=(1515), size=20,
             # 수평 정렬 / 수직 정렬
             ha='center', va='center',
             # 화살표와 박스의 속성을 설정
             arrowprops=arrow, bbox=bboxType, )
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs

우선 아까 scatter 함수를 연습할 때 사용한 데이터로 산포도 그래프를 그려줍니다. 저는 이 데이터 중에 (11, 10) 지점이 중요한 데이터라고 생각하고 표시를 해둘려고 합니다. 이럴 때 annotate() 함수를 사용합니다. 속성이 꽤 많이 필요한걸 볼 수 있는데요. 주석에 정리했지만 아래에 다시 정리 해보았습니다.


xy : 표시할 데이터가 있는 곳입니다. 쉽게 도착점이라고 생각합시다

xytext : 텍스트 박스를 둘 위치를 지정합니다. 이 때 박스의 중앙점의 좌표를 입력하면 됩니다.

ha / va : 각각 수평 / 수직 방향 정렬 속성입니다.

arrowprop : 화살표의 속성을 지정합니다. dict() 함수를 이용하여 지정합니다.

bbox : 텍스트 박스의 속성을 지정합니다. 마친가지로 dict()함수로 지정합니다.


박스와 화살표는 다양한 모양과 속성들이 있습니다. 모든 속성을 블로그에 옮겨 적는데에는 한계가 있어서 api 링크를 납깁니다. 긴 글 읽어주셔서 감사합니다!!


,

Python2.x를 사용하여 구현하였습니다. 3.x버전을 사용하시는 분들은 참고하시길 바랍니다.


1.  k-NN 알고리즘

가장 가까운 이웃을 찾아라

k-NN 알고리즘은 기존의 데이터들은 각각의 라벨이 붙어 있고, 라벨이 없는 임의의 데이터 X가 과연 어떤 종류에 들어가는지 분류하는 알고리즘이다. 이때 X와 기존의 데이터들의 거리를 모두 구한 후 그중 가장 가까운 k 개의 데이터의 라벨들 중 가장 많은 수의 라벨이 X 데이터의 라벨로 결정하게 하는 알고리즘이다.


일반적으로 k-NN 알고리즘을 적용하기 위해서는 수치형 값을 사용하는 편인데, 임의의 데이터와 기존 데이간의 거리를 계산하기 위해서이다. 특별히 훈련하는 과정도 없는 특징도 바로 모든 기존의 데이터와 거리를 계산하는 특징으로 나타나는 것이다.


2.  NumPy로 구현


알고리즘의 구현 순서

1) 파일로부터 데이터를 수집 

2) 정규화(Normalization)

3) 가시화(Visualization)

4) 테스트 데이터와 모든 데이터 간의 거리 계산 후 가장 근접한 k개 데이터 선정후 다수결로 결정

   (k-NN 알고리즘)


1) 파일로부터 데이터 수집

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from numpy import *
import operator
 
def file2matrix(filename):
    # 데이터에 저장된 Iris의 종류는 총 3개입니다.
    # 각각을 숫자로 매핑한 Dictionary를 구현합니다.
    Iris_dict = {"Iris-setosa" : 1"Iris-versicolor" : 2"Iris-virginica" : 3}
    
    # 파일을 열고 라인 단위로 파일을 읽어 드립니다.
    fr = open(filename)
    TextLine = fr.readline()
    
    # numOfData  : 데이터의 수를 의미
    # featMatrix : 파일로 읽어드린 데이터를 행렬로 저장하기 위해 영행렬로 초기화합니다.
    #              이 때 Feacture의 수가 총 3개이므로 Data의 수 X 3의 행렬을 만듭니다.  
    numOfData = len(TextLine)
    featMatrix = zeros((numOfData, 3))
 
    # labelVector : dataMatrix의 각 행에 대응하는 lebel을 저장합니다.
    labelVector = []
    index = 0
    
    for line in TextLine:
        # 각각의 데이터는 ','으로 구분되어 있습니다.
        # 따라서 ','을 기준으로 분리하여 줍니다.
        splitData = line.split(',')    
        # 파일에 데이터가 "종류,특징1,특징2,특징3"으로 저장되어 있으므로
        # 두번째 열부터 끝까지가 Feature입니다. 
        featMatrix[index, : ] = splitData[1:]
        labelVector.append(Iris_dictionary.get(splitData[0]))
        index += 1
    
    return featMatrix, labelVector   
cs


2) 정규화

정규화를 하는 이유는 각각의 특징(Feature)들이 서로 다른 단위를 가진다는 점에서 시작합니다. 
만약 달리기를 하는 사람의 데이터가 아래와 같이 있다고 가정하여 봅시다

Feature 1 : 지칠 때까지 최대한 달린 거리
Feature 2 : 100m 달리기 기록

Data 1 :  (10,300m, 13.40초, 장거리 선수)
Data 2 :  (5,000m, 11.30초, 단거리 선수)

Data 1 ~ Data의 거리 :

     


kNN은 Feature 간의 거리를 계산하는 알고리즘입니다. Data 1 ~ Data 2간의 거리 값에서 가장 큰 비중을

차지하는 것은 Feature 1이 되어 버리겠죠. 왜냐면 Feature를 구성하는 단위의 크기가 어마하게 차이가

나기 때문입니다. 100m 달리기 기록을 아무리 단축하여도 거리에는 큰 영향을 못 준다.


데이터를 분석하는 사람의 판단으로 Feature 1이나 Feature 2나 동일하게 중요하다고 판단을 내리는 경우

위의 같은 사례는 썩 좋아하는 상황이 아닙니다. 따라서 데이터를 일정 범위로 줄여 버리는 과정인 정규화 과정을 거칩니다.


정규화


분모에는 각 Feature의 범위가 들어가고 분자에는 data Set에 저장되어있는 Feature 값과 최소값의 차이값이 들어갑니다. 이 식을 활용해서 파이썬으로 마찬가지로 구현하면 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 위의 함수에서 이어서 사용합니다.
def normarlization(dataSet):
    # dataSet 중의 최소 값과 최대 값을 구합니다
    # 그리고 두 값의 차이로 범위를 구해줍니다.
    minVal = dataSet.min(0)
    maxVal = dataSet.max(0)
    ranges = maxVal - minVal
 
    # 정규화 결과를 저장하는 행렬을 준비합니다.
    normalDataSet = zeros(shape(dataSet))
    
    # dataSet의 행의 갯수를 저장합니다.
    rowCnt = dataSet.shape[0]
 
    # 정규화 공식에 맞게 적용하였습니다.
    normalDataSet =(dataSet - tile(minVal, (rowCnt, 1))) / tile(ranges, (rowCnt, 1)) 
    
    return normalDataSet
cs


3) 가시화

보기에도 좋은 떡이 맛도 좋다는 말이 있죠? 데이터도 숫자로 되어 있으면 뭐가 뭔지 의미를 쉽게 알 수 없을
겁니다. 파이썬에는 matplotlib라는 좋은 그래프 그리는 라이브러리가 있으니까 그것을 적극적으로 활용해 봅시다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 위의 함수에서 이어서 사용합니다.
def visualizeData(dataSet, labels):
    import matplotlib
    matplotlib.use('Agg')
 
    import matplotlib.pyplot as plt
 
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,0], dataSet[ :, 1], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot1.svg')
    
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,0], dataSet[ :, 2], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot2.svg')
    
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,1], dataSet[ :, 2], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot3.svg')
 
cs


짜잔.. 숫자로 봤을 때 보다 훨씬 좋죠?


4) k-NN 알고리즘

이제 마지막으로 k-NN 분류기를 구현해보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 위의 함수에서 이어서 사용합니다.
def classify0(testDataSet, trainDataSet, labels, k):
    trainDataSetSize = trainDataSet.shape[0]
    
    # 테스트 데이터와 훈련 데이터간의 거리를 계산하는 과정입니다.
    diffMat = tile(testDataSet, (trainDataSetSize, 1)) - trainDataSet 
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    
    # 오름차순으로 argsort를 합니다.
    # 거리가 짧은 순서대로 정렬이 됩니다.
    sortedDistIndex = distances.argsort()     
    
    # 다수결의 결과를 저장하는 곳입니다.
    classCount={}
    
    for i in range(k):
        # 본격 개표 방송
        # 오름차순으로 정렬한 데이터 중 k번째 데이터까지 라벨을 확인한 후
        # 투표 결과를 저장합니다.
        votelabel = labels[sortedDistIndex[i]]
        classCount[votelabel] = classCount.get(votelabel,0+ 1
    
    # 내림차순으로 정렬합니다
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    
    return sortedClassCount[0][0]   
 
cs


3.  마치면서..

k-NN은 단순하지만 데이터를 분류하는데 효과적인 알고리즘입니다. 하지만 데이터를 전부다 순회하면서

거리를 측정해야 하기 때문에 대규모의 데이터에 적용할 때는 시간이 오래걸리는 단점이 있습니다.

앞으로 여러 가지 알고리즘을 공부해서 포스팅 하겠습니다.


정말 긴 글 읽어 주셔서 감사합니다. 밑에 하트 한 번만 눌러주시면 저에게 큰 힘이 됩니다 ㅠㅠ



,