brunch

You can make anything
by writing

C.S.Lewis

by 유윤식 Jun 14. 2024

Python: forust 소개

#forust #RandomForust #앙상블모델

짜잔!


https://jinlow.github.io/forust/#python-api-reference


사용 예제 조금 튀들어서 다시 만들어 봤는데

사용하기 너무 쉽고 간편한 듯 해서 기록한다.


먼저,

>> !pip install forust seaborn


코드는 간단한데


import ssl

import polars as pl

import matplotlib.pyplot as plt


from forust import GradientBooster

from seaborn import lineplot

from seaborn import load_dataset

from sklearn.preprocessing import LabelEncoder


ssl._create_default_https_context = ssl._create_unverified_context


ssl 부분 체크가 필요하다.


이어서,

데이터가 조금 허접해서 파생 데이터 3개 정도 간단하게 만들고


df = pl.DataFrame(load_dataset("titanic"))

le = LabelEncoder()


df = df.with_columns([

    pl.Series(le.fit_transform(np.array(df.select(pl.col('who'))).ravel())).alias('who_encode'),

    pl.Series(le.fit_transform(np.array(df.select(pl.col('class'))).ravel())).alias('class_encode'),

    pl.Series(le.fit_transform(np.array(df.select(pl.col('embarked'))).ravel())).alias('embarked_encode'),

])


잘 만든 데이터에서 머신러닝에 필요한 뉴메릭(?)... 숫자 데이터만 선택해서 가져온다.



X1 = df.select([

        pl.col(

            pl.Int8, pl.Int16, pl.Int32, pl.Int64, pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64, 

            pl.Float32, pl.Float64

        ).exclude("survived")

    ]).to_pandas()


y1 = df.select(pl.col("survived")).to_pandas()


*_encode 컬럼이 새로 만든 것이다.


우리는 보통 XGBoost 라이브러리를 주로 사용했을 것 같은데

Forust 도 못지않은 좋은 성능과 장점을 가지고 있는 것 같다.


첫번째 모델을 만들고 FIT 시켜보면


model = GradientBooster(

    objective_type="LogLoss",

)


model.fit(X_train, y_train)


간단하게 테스트 데이터로 정확도를 확인해보면,


res = model.predict(X_test)

res = np.where(res > 0, 1, 0)

print(str(np.round(np.mean(res == y_test) * 100, decimals=2)) + '%')


>> 77.13%


라는 결과를 얻을 수 있었는데


재미있는 부분은 바로 monotone 을 사용해서 글로벌한 정확도를 끌어올릴 수 있다는 부분이다.


그에 앞서,

어떤 Feature 를 monotone 대상으로 볼 것인지 간단한(?) 엔지니어링 작업이 선행되어야 한다.

사실 간단하지 않지만 Forust 에서 제공하는 partial_dependence 를 사용하기 때문에 간단하다고 표현했다.



pd_values = model.partial_dependence(X=X1.to_pandas(), feature="age", samples=None)


fig = lineplot(x=pd_values[:,0], y=pd_values[:,1],)

plt.title("Partial Dependence Plot")

plt.xlabel("Age")

plt.ylabel("Log Odds")


참고로 코드는 홈페이지 예제에 똑같이! 나와있다.

특별히 age 컬럼에 대해서 model 에 어떤 영향도를 주는지 확인한다.

현업에서는 보통 이런 들쭉날쭉한 방정식에 대해서 1차 또는 2차 방적식으로 간단하게 표현해서 사용하는데

이게 바로 monotone 이다.


두번째 모델은 monotone 옵션을 추가해서 재작성 해보도록 한다.


model2 = GradientBooster(

    objective_type="LogLoss",

    monotone_constraints={"age": -1},

)


model2.fit(X_train, y_train)


pd_values = model2.partial_dependence(X=X_train, feature="age")

fig = lineplot(

    x=pd_values[:, 0],

    y=pd_values[:, 1],

)

plt.title("Partial Dependence Plot with Monotonicity")

plt.xlabel("Age")

plt.ylabel("Log Odds")


이러면 age 의 model 에 대한 영향도가 균일해질텐데 실제로 결과를 보면

뭔가 선이 스무스하게 y=-x 형태터럼 보인다.


두번째 모델로 정확도를 확인해보면


res = model2.predict(X_test)

res = np.where(res > 0, 1, 0)

print(str(np.round(np.mean(res == y_test) * 100, decimals=2)) + '%')


>> 81.61%


쬐~~끔 올랐다.

그래도 뭔가 변화가 있는 결과라는 부분에서 재미는 있다.


P.S.

데이터에 여러가지 파생데이터를 붙여서 해봤는데

monotone 사용이 몇 % 정도 이점을 주는 것으로 확인했다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari