728x90

❤️ 배운 것

2023-10-13 Decision Tree Algorithm with IGR

빈 데이터 채우기

h1_slope 노드는 flat, moderate, steep 3가지로 구분이 되어야 하는데
다른 종류는 모두 인스턴스 데이터가 있지만,
moderate에 해당하는 인스턴스 데이터가 없음

이 경우에는 기존 상위 노드에 chapparal이 2개, conifer가 1개가 있으니까
가장 많은 데이터인 chapparal을 선택하여 채움
(moderate가 나왔을 때도 분류를 해야하기 때문에)
-> 일단 다수데이터로 분류되도록 하는 것임

Information Gain Ratio

information gain을 기준으로 분기용 피처를 선정할 경우
최적화가 아닌데, 잘게 쪼개어져서 I.G가 높게 나오는 경우를 선택하는 한계점이 있음

Information Gain Ratio = IG / IV

IV란 target 대신 feature 기준으로 entropy를 구한 값

ex)
with whom으로 자르면
t1~ t8 : 1개
t9 : 2개

 

def get_iv():  
    probs = [1/10, 1/10, 1/10, 1/10, 1/10, 1/10, 1/10, 1/10, 1/10, 2/10]  
    probs = np.array(probs)  
    entropy = -np.sum(probs * np.log2(probs))  
    print(entropy)  
    # 3.4541209043760985

 

어떤 metric을 사용하는 지에 따라 decision tree 모양도 달라지고,
예측값도 달라진다.

metric은 Information Gain, Information Gain Ratio 등..

 

Gini index

 

$$
Gini(t, D) = 1 - \sum_{l=levels(t)}P(t=l)^2
$$

IGR 기준 decision Tree

def entropy(p: list):  
    tot = sum(p)  
    p = np.array(p).astype(dtype='float64')  
    p /= tot  
    entropy = -np.sum(p * np.log2(p))  
    return entropy  


def information_gain(parent, child):  
    parent_entropy = entropy(parent)  
    l_parent = float(sum(parent))  

    partition_entropy = []  

    for ele in child:  
        l_child = float(sum(ele))  
        part_ent = entropy(ele)  

        curr_ent = l_child / l_parent * part_ent  
        partition_entropy.append(curr_ent)  

    final_entropy = sum(partition_entropy)  
    ig = parent_entropy - final_entropy  

    return ig  


def get_igr(parent, child):  
    ig = information_gain(parent=parent, child=child)  
    child_all = [sum(x) for x in child]  
    iv = entropy(child_all)  
    igr = ig / iv  
    return igr  


def get_igr_idx(X, y, col_names):  
    ig_list = list()  
    h_list = list()  
    parent_uniques, parent_cnts = np.unique(y, return_counts=True)  

    for i in range(X.shape[1]):  
        curr = X[:, i]  
        uq = np.unique(curr)  
        children = list()  
        for ele in uq:  
            ele_idx = (curr == ele)  
            curr_y = y[ele_idx]  
            uniq, cnts = np.unique(curr_y, return_counts=True)  
            # child = [[6], [1, 3]]  
            children.append(cnts)  

        e = information_gain(parent=parent_cnts, child=children)  
        ig_list.append(e)  

    # get iv by features  
    for i in range(X.shape[1]):  
        curr = X[:, i]  
        uniques, cnts = np.unique(curr, return_counts=True)  

        curr_entropy = entropy(cnts)  
        h_list.append(curr_entropy)  

    gr_list = np.array(ig_list) / np.array(h_list)  
    print("col: ", col_names)  
    print("gr: ", gr_list)  
    max_idx = np.argmax(gr_list)  

    return max_idx  


def decision_tree_by_igr(data, col_names):  
    X = data[:, 1:-1]  
    y = data[:, -1]  
    col_names = col_names[1:-1]  

    # root node separation  
    max_idx = get_igr_idx(X, y, col_names)  
    print(f"h1 node: idx {max_idx} {col_names[max_idx]}")  
    # flat-fin, moderate-fin, steep-3types  

    print("=" * 30)  

    # data filtration by slope - steep  
    to_remain = (X[:, max_idx] == 'steep')  
    X1 = X[to_remain]  
    X1 = np.delete(X1, max_idx, axis=1)  
    y1 = y[to_remain]  
    col_names.pop(max_idx)  

    # h1 separation  
    max_idx = get_igr_idx(X1, y1, col_names)  
    print(f"h2 node: idx {max_idx} {col_names[max_idx]}")  
    # low-filled with chapparal, medium-2types, high-fin, highest-fin  

    print("=" * 30)  

    # data filtration by elevation - medium  
    to_remain = (X1[:, max_idx] == 'medium')  
    X2 = X1[to_remain]  
    X2 = np.delete(X2, max_idx, axis=1)  
    y2 = y1[to_remain]  
    col_names.pop(max_idx)  

    # h2 separation  
    max_idx = get_igr_idx(X2, y2, col_names)  
    print(f"h3 node: idx {max_idx} {col_names[max_idx]}")  


def main_routine():  
    df = pd.read_csv('../data/vegetation.csv')  
    col_names = df.columns.to_list()  
    data = df.to_numpy()  
    decision_tree_by_igr(data, col_names)


if __name__ == '__main__':  
    main_routine()

코드 기전

main_routine(): 메인 루틴, csv 데이터를 읽어서 decision tree를 계산하는 코드 실행


decision_tree_by_igr(data, col_names): information gain ratio를 기준으로 의사결정나무를 계산하는 기능
루트 노드 계산 -> h1노드 계산 -> h2노드 계산

 

get_igr_idx(X, y, col_names): 피처데이터 X와 타겟 y으로 information gain ratio를 계산, igr 최댓값의 index를 리턴
ig list와 h list를 구하여 나눔

 

information_gain(parent, child): parent와 child로 information gain을 구하는 기능


entropy(p: list): 단일 entropy를 구하는 기능

 

   ID  STREAM     SLOPE ELEVATION VEGETATION
0   1   False     steep      high  chapparal
1   2    True  moderate       low   riparian
2   3    True     steep    medium   riparian
3   4   False     steep    medium  chapparal
4   5   False      flat      high    conifer
5   6    True     steep   highest    conifer
6   7    True     steep      high  chapparal

col:  ['STREAM', 'SLOPE', 'ELEVATION']
gr:  [0.31054583 0.50260164 0.47622714]
h1 node: idx 1 SLOPE
==============================
col:  ['STREAM', 'ELEVATION']
gr:  [0.43253807 0.63797403]
h2 node: idx 1 ELEVATION
==============================
col:  ['STREAM']
gr:  [1.]
h3 node: idx 0 STREAM

Process finished with exit code 0

 

DATASET

ID STREAM SLOPE ELEVATION VEGETATION
1 FALSE steep high chapparal
2 TRUE moderate low riparian
3 TRUE steep medium riparian
4 FALSE steep medium chapparal
5 FALSE flat high conifer
6 TRUE steep highest conifer
7 TRUE steep high chapparal

 

IGR Metric Decision Tree

 

IGR 기준 Decision Tree

 

 

 

💛 배운점/느낀점

- 여러 계산이 섞인 IGR을 수학적으로 이해하고, 코드로 구현해내는 과정이 시간이 많이 소요되었지만 의미있는 훈련이었다.

- 스크립트식으로 코드를 작성하지 않고, 시간 안에 최대한 가능한 만큼 함수화하여 코드를 작성해보면서 머신러닝적 사고방식 및 코딩을 연습하는 계기가 되었다.

- 엑셀 피벗테이블로 데이터를 filtration하여 직접 diagram까지 그려보니 데이터 분기를 확실하게 확인할 수 있었다.

반응형