🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Decision Tree 및 시각화 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 12. 6. · 11 Views

Decision Tree 및 시각화 완벽 가이드

머신러닝의 가장 직관적인 알고리즘인 Decision Tree의 개념부터 시각화까지 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 실무에서 바로 활용할 수 있는 코드 예제와 함께 트리 구조를 눈으로 확인하는 방법을 배워봅니다.


목차

  1. Decision_Tree_기본_개념
  2. 트리_구조_이해하기
  3. Graphviz로_트리_시각화
  4. plot_tree로_간편하게_시각화
  5. 텍스트로_트리_규칙_출력
  6. 특성_중요도_시각화
  7. 회귀_트리_시각화
  8. 하이퍼파라미터_튜닝_효과_시각화
  9. 결정_경계_시각화
  10. 과적합과_가지치기_시각화

1. Decision Tree 기본 개념

어느 날 김개발 씨는 고객 이탈 예측 모델을 만들어 달라는 요청을 받았습니다. 머신러닝을 처음 접하는 그에게 선배 박시니어 씨가 다가왔습니다.

"처음이라면 Decision Tree부터 시작해보세요. 가장 이해하기 쉬운 알고리즘이거든요."

Decision Tree는 한마디로 스무고개 게임과 같습니다. "이 고객의 나이가 30세 이상인가요?", "월 구매액이 10만원 이상인가요?"처럼 예/아니오로 답할 수 있는 질문을 연속으로 던져서 최종 결론에 도달합니다.

이 과정이 마치 나무가 가지를 뻗어나가는 모양과 닮아서 Decision Tree라는 이름이 붙었습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris

# 붓꽃 데이터 로드
iris = load_iris()
X, y = iris.data, iris.target

# Decision Tree 모델 생성
model = DecisionTreeClassifier(max_depth=3, random_state=42)

# 모델 학습 - 여기서 트리가 만들어집니다
model.fit(X, y)

# 새로운 데이터 예측
prediction = model.predict([[5.1, 3.5, 1.4, 0.2]])
print(f"예측 결과: {iris.target_names[prediction[0]]}")

김개발 씨는 입사 6개월 차 데이터 분석가입니다. 오늘 팀장님으로부터 특별한 미션을 받았습니다.

"다음 달에 해지할 것 같은 고객을 미리 예측할 수 있는 모델을 만들어주세요." 머신러닝이라는 단어만 들어도 막막한 김개발 씨였습니다. 옆자리 선배 박시니어 씨가 커피 한 잔을 건네며 말했습니다.

"겁먹지 마세요. Decision Tree부터 시작하면 됩니다.

이건 우리가 일상에서 늘 하는 의사결정과 똑같거든요." 그렇다면 Decision Tree란 정확히 무엇일까요? 쉽게 비유하자면, Decision Tree는 마치 병원 진료와 같습니다.

의사 선생님이 "열이 있나요?"라고 물어봅니다. "네"라고 대답하면 "기침도 하나요?"라고 다음 질문을 던집니다.

이렇게 질문을 이어가다 보면 결국 "감기입니다" 또는 "독감입니다"라는 진단에 도달하게 됩니다. Decision Tree도 정확히 이런 방식으로 작동합니다.

Decision Tree가 없던 시절에는 어땠을까요? 개발자들은 수많은 if-else 문을 직접 작성해야 했습니다.

"나이가 30 이상이고, 구매액이 10만원 미만이고, 최근 접속이 30일 이전이면..."처럼 조건을 일일이 손으로 정했습니다. 문제는 어떤 조건이 중요한지, 어떤 순서로 물어봐야 하는지 사람이 결정해야 했다는 점입니다.

바로 이런 문제를 해결하기 위해 Decision Tree 알고리즘이 등장했습니다. Decision Tree는 데이터를 보고 스스로 최적의 질문 순서를 찾아냅니다.

어떤 특성이 가장 중요한지, 어떤 기준값으로 나눠야 가장 정확한 예측이 가능한지를 자동으로 학습합니다. 개발자는 데이터만 넣어주면 됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 DecisionTreeClassifier를 불러옵니다.

이것이 바로 Decision Tree 모델의 핵심 클래스입니다. max_depth=3은 트리의 최대 깊이를 3단계로 제한한다는 의미입니다.

질문을 최대 3번만 하겠다는 뜻이죠. fit 메서드를 호출하면 모델이 데이터를 학습합니다.

이 순간 트리 구조가 만들어집니다. 실제 현업에서는 어떻게 활용할까요?

금융권에서는 대출 승인 여부를 결정할 때 Decision Tree를 활용합니다. 고객의 소득, 신용점수, 부채비율 등을 기준으로 "승인" 또는 "거절"을 예측합니다.

무엇보다 좋은 점은 왜 그런 결정을 내렸는지 설명할 수 있다는 것입니다. "신용점수가 600점 미만이고 부채비율이 40%를 초과했기 때문에 거절되었습니다"라고 고객에게 명확히 안내할 수 있습니다.

하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수 중 하나는 max_depth를 너무 크게 설정하는 것입니다.

트리가 너무 깊어지면 학습 데이터에는 완벽하게 맞지만, 새로운 데이터에서는 오히려 성능이 떨어지는 과적합 현상이 발생합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 스무고개랑 똑같은 원리군요!

이 정도면 저도 할 수 있을 것 같아요."

실전 팁

💡 - max_depth는 3~5 정도로 시작하고, 검증 데이터 성능을 보면서 조절하세요

  • random_state를 고정하면 매번 같은 결과를 얻을 수 있어 디버깅에 유리합니다

2. 트리 구조 이해하기

김개발 씨가 첫 번째 Decision Tree 모델을 만들었습니다. 그런데 막상 모델이 어떻게 생겼는지, 어떤 질문을 던지는지 전혀 보이지 않았습니다.

"이 모델이 정말 제대로 학습한 건지 어떻게 알 수 있죠?" 박시니어 씨가 미소를 지었습니다. "트리 구조를 직접 들여다보면 됩니다."

Decision Tree의 가장 큰 장점은 해석 가능성입니다. 다른 머신러닝 알고리즘과 달리 모델 내부를 들여다볼 수 있습니다.

트리는 루트 노드, 내부 노드, 리프 노드로 구성되며 각 노드에서 어떤 특성으로 어떤 기준값을 사용해 분기했는지 확인할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris

iris = load_iris()
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(iris.data, iris.target)

# 트리 구조 정보 확인
print(f"트리 깊이: {model.get_depth()}")
print(f"리프 노드 수: {model.get_n_leaves()}")
print(f"특성 중요도: {model.feature_importances_}")

# 가장 중요한 특성 찾기
important_idx = model.feature_importances_.argmax()
print(f"가장 중요한 특성: {iris.feature_names[important_idx]}")

김개발 씨는 모델을 만들었지만 뭔가 찜찜했습니다. 블랙박스처럼 느껴졌기 때문입니다.

데이터를 넣으면 결과가 나오는데, 왜 그런 결과가 나왔는지 알 수 없었습니다. 박시니어 씨가 화이트보드 앞으로 김개발 씨를 데려갔습니다.

"Decision Tree의 가장 큰 매력은 바로 투명성이에요. 모델이 어떻게 결정을 내리는지 낱낱이 볼 수 있거든요." 그렇다면 트리 구조란 정확히 무엇일까요?

쉽게 비유하자면, 트리 구조는 마치 회사 조직도와 같습니다. 맨 위에 CEO가 있고, 그 아래에 본부장들이, 그 아래에 팀장들이 있습니다.

Decision Tree도 마찬가지입니다. 맨 위에 루트 노드가 있고, 중간에 내부 노드들이 있으며, 맨 아래에 최종 결정을 내리는 리프 노드가 있습니다.

각 노드가 하는 일은 무엇일까요? 루트 노드는 가장 중요한 첫 번째 질문을 던집니다.

붓꽃 데이터의 경우 "꽃잎 길이가 2.45cm 이하인가요?"라는 질문이 될 수 있습니다. 내부 노드는 두 번째, 세 번째 질문을 담당합니다.

그리고 리프 노드에 도달하면 "이 꽃은 setosa입니다"라는 최종 예측을 반환합니다. 위의 코드를 살펴보겠습니다.

get_depth 메서드는 트리가 얼마나 깊은지 알려줍니다. 깊이가 3이라면 최대 3번의 질문을 거쳐 결론에 도달한다는 의미입니다.

get_n_leaves는 리프 노드의 개수입니다. 리프 노드가 많을수록 더 세분화된 예측이 가능하지만, 너무 많으면 과적합의 신호일 수 있습니다.

가장 흥미로운 것은 feature_importances_ 속성입니다. 이 속성은 각 특성이 예측에 얼마나 기여했는지를 0과 1 사이의 숫자로 보여줍니다.

붓꽃 데이터에서는 보통 꽃잎 길이와 꽃잎 너비가 가장 높은 중요도를 가집니다. 이 정보를 통해 "아, 꽃을 구분할 때는 꽃잎이 가장 중요하구나"라는 통찰을 얻을 수 있습니다.

실제 현업에서 이 기능은 매우 유용합니다. 마케팅 팀에서 "고객 이탈에 가장 큰 영향을 미치는 요소가 뭔가요?"라고 물어보면, feature_importances_를 확인해서 "최근 30일 접속 횟수가 가장 중요합니다.

그 다음이 고객 등급이고요"라고 데이터에 기반한 답변을 할 수 있습니다. 하지만 주의할 점이 있습니다.

feature_importances_는 상대적인 값입니다. 0.5라고 해서 50%의 영향력을 가진다는 의미가 아닙니다.

단지 다른 특성들에 비해 상대적으로 더 중요하다는 것을 나타낼 뿐입니다. 또한 상관관계가 높은 특성들이 있으면 중요도가 분산될 수 있습니다.

김개발 씨가 눈을 반짝였습니다. "이렇게 모델 내부를 볼 수 있다니, 정말 투명하네요!"

실전 팁

💡 - feature_importances_는 모델 해석의 첫 걸음입니다

  • 중요도가 0인 특성은 모델이 전혀 사용하지 않았다는 의미이므로 제거를 고려해보세요

3. Graphviz로 트리 시각화

"근데 선배, 숫자만 봐서는 트리가 어떻게 생겼는지 상상이 안 돼요." 김개발 씨의 말에 박시니어 씨가 고개를 끄덕였습니다. "그렇죠.

그래서 시각화가 필요합니다. Graphviz를 사용하면 트리를 그림으로 볼 수 있어요."

Graphviz는 그래프 구조를 시각화하는 도구입니다. scikit-learn의 export_graphviz 함수와 함께 사용하면 Decision Tree를 멋진 다이어그램으로 변환할 수 있습니다.

각 노드의 분기 조건, 샘플 수, 클래스 분포까지 한눈에 확인할 수 있어 모델 이해에 큰 도움이 됩니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.datasets import load_iris
import graphviz

iris = load_iris()
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(iris.data, iris.target)

# DOT 형식으로 트리 내보내기
dot_data = export_graphviz(
    model,
    feature_names=iris.feature_names,
    class_names=iris.target_names,
    filled=True,          # 노드에 색상 채우기
    rounded=True          # 모서리 둥글게
)

# 시각화 생성 및 저장
graph = graphviz.Source(dot_data)
graph.render("iris_tree", format="png")

김개발 씨는 트리의 깊이가 3이고 리프 노드가 5개라는 것을 알게 되었습니다. 하지만 여전히 머릿속에 그림이 그려지지 않았습니다.

마치 지도 없이 길을 찾는 기분이었습니다. 박시니어 씨가 모니터를 가리켰습니다.

"자, 이제 진짜 마법을 보여줄게요. 트리를 그림으로 그려봅시다." Graphviz란 무엇일까요?

쉽게 비유하자면, Graphviz는 마치 건축 도면 프로그램과 같습니다. 건축가가 설계 데이터를 입력하면 멋진 청사진이 나오듯이, Graphviz에 트리 정보를 넣으면 보기 좋은 다이어그램이 만들어집니다.

위의 코드를 자세히 살펴보겠습니다. export_graphviz 함수가 핵심입니다.

이 함수는 학습된 모델을 DOT라는 그래프 표현 언어로 변환합니다. feature_names 파라미터를 넣으면 "feature_0" 대신 "petal length (cm)"처럼 읽기 쉬운 이름이 표시됩니다.

class_names도 마찬가지로 "class_0" 대신 "setosa"로 보여줍니다. filled=True 옵션이 특히 유용합니다.

이 옵션을 켜면 각 노드가 색상으로 채워집니다. 색상은 해당 노드에서 가장 많은 클래스를 나타냅니다.

예를 들어 setosa가 많은 노드는 주황색, versicolor가 많은 노드는 초록색으로 표시될 수 있습니다. 색상의 진하기는 순도를 나타냅니다.

색이 진할수록 한 클래스가 압도적이라는 의미입니다. 시각화된 트리에서 무엇을 볼 수 있을까요?

각 노드에는 여러 정보가 담겨 있습니다. 맨 위에는 분기 조건이 있습니다.

"petal length <= 2.45"처럼 어떤 기준으로 나누는지 보여줍니다. samples는 해당 노드에 도달한 데이터 개수입니다.

value는 각 클래스별 샘플 수를 배열로 보여줍니다. class는 현재 노드에서 예측하는 클래스입니다.

실무에서 이 시각화는 어떻게 활용될까요? 프레젠테이션에서 비개발자에게 모델을 설명할 때 매우 유용합니다.

"보시다시피 첫 번째로 고객의 최근 접속일을 확인합니다. 30일이 넘었다면 이탈 위험군으로 분류됩니다"라고 그림을 보여주며 설명할 수 있습니다.

경영진도 이해할 수 있는 직관적인 자료가 됩니다. 하지만 주의할 점이 있습니다.

Graphviz는 별도로 설치해야 합니다. pip install graphviz만으로는 부족하고, 시스템에 Graphviz 프로그램도 설치되어 있어야 합니다.

우분투에서는 apt-get install graphviz, 맥에서는 brew install graphviz로 설치할 수 있습니다. 김개발 씨의 눈이 커졌습니다.

화면에 나타난 트리 다이어그램을 보며 감탄했습니다. "와, 이렇게 보니까 모델이 어떻게 생각하는지 한눈에 들어오네요!"

실전 팁

💡 - 트리가 너무 크면 시각화가 복잡해지므로 max_depth를 적절히 제한하세요

  • graph.render() 대신 graph.view()를 사용하면 바로 이미지 뷰어가 열립니다

4. plot tree로 간편하게 시각화

"Graphviz 설치가 좀 번거롭네요." 김개발 씨가 투덜거렸습니다. 박시니어 씨가 웃으며 말했습니다.

"그럴 줄 알았어요. 사실 더 간단한 방법이 있어요.

matplotlib만 있으면 됩니다."

scikit-learn 0.21 버전부터 plot_tree 함수가 추가되었습니다. 별도의 Graphviz 설치 없이 matplotlib만으로 Decision Tree를 시각화할 수 있습니다.

빠르게 트리를 확인하고 싶을 때 매우 유용한 방법입니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

iris = load_iris()
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(iris.data, iris.target)

# matplotlib으로 트리 시각화
plt.figure(figsize=(20, 10))
plot_tree(
    model,
    feature_names=iris.feature_names,
    class_names=iris.target_names,
    filled=True,
    fontsize=10
)
plt.tight_layout()
plt.savefig("tree_plot.png", dpi=150)

김개발 씨는 Graphviz 설치에 애를 먹고 있었습니다. 회사 보안 정책 때문에 외부 프로그램 설치가 까다로웠기 때문입니다.

이때 박시니어 씨가 구원의 손길을 내밀었습니다. "matplotlib은 이미 설치되어 있죠?

그럼 plot_tree를 쓰면 됩니다." plot_tree란 무엇일까요? 쉽게 비유하자면, plot_tree는 마치 폴라로이드 카메라와 같습니다.

Graphviz가 전문 스튜디오에서 사진을 인화하는 것이라면, plot_tree는 그 자리에서 바로 찍어서 바로 보는 즉석 사진입니다. 품질은 조금 다를 수 있지만, 빠르고 간편합니다.

위의 코드를 살펴보겠습니다. 먼저 **plt.figure(figsize=(20, 10))**으로 충분히 큰 캔버스를 준비합니다.

트리가 복잡할수록 큰 크기가 필요합니다. plot_tree 함수의 파라미터는 export_graphviz와 거의 동일합니다.

feature_names, class_names, filled 옵션을 똑같이 사용할 수 있습니다. fontsize 파라미터가 중요합니다.

트리가 크면 글자가 겹쳐서 읽기 어려워질 수 있습니다. fontsize를 조절해서 가독성을 확보하세요.

너무 작으면 안 보이고, 너무 크면 겹칩니다. 보통 8~12 사이가 적당합니다.

Graphviz와 plot_tree, 어떤 것을 선택해야 할까요? 빠르게 확인하고 싶다면 plot_tree가 좋습니다.

별도 설치 없이 바로 사용할 수 있고, Jupyter Notebook에서 인라인으로 바로 볼 수 있습니다. 반면 발표 자료나 보고서에 넣을 고품질 이미지가 필요하다면 Graphviz가 더 나은 선택입니다.

실무에서는 상황에 따라 사용합니다. 개발 중에 모델을 확인할 때는 plot_tree로 빠르게 보고, 최종 결과물을 만들 때는 Graphviz로 예쁘게 다듬습니다.

두 도구를 모두 알아두면 상황에 맞게 선택할 수 있습니다. 주의할 점이 있습니다.

plot_tree는 트리가 깊어지면 가독성이 급격히 떨어집니다. max_depth가 5 이상이면 노드들이 서로 겹쳐서 거의 알아볼 수 없게 됩니다.

이런 경우에는 일부만 시각화하거나, Graphviz를 사용하는 것이 좋습니다. 김개발 씨가 코드를 실행하자 바로 트리 그림이 나타났습니다.

"오, 이건 진짜 간단하네요! 개발할 때는 이걸 쓰고, 보고서 쓸 때만 Graphviz 쓰면 되겠어요."

실전 팁

💡 - Jupyter Notebook에서는 plt.show() 대신 그냥 plot_tree()만 호출해도 됩니다

  • dpi를 높이면 더 선명한 이미지를 얻을 수 있습니다

5. 텍스트로 트리 규칙 출력

"그런데 선배, 이 트리를 코드로 변환하고 싶을 때가 있어요. 규칙을 텍스트로 볼 수는 없나요?" 김개발 씨의 질문에 박시니어 씨가 고개를 끄덕였습니다.

"당연히 있죠. export_text 함수를 사용하면 됩니다."

export_text 함수는 Decision Tree를 텍스트 형태의 규칙으로 출력합니다. 이미지 없이도 트리의 분기 구조를 명확하게 파악할 수 있으며, 규칙을 문서화하거나 다른 시스템에 적용할 때 유용합니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris

iris = load_iris()
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(iris.data, iris.target)

# 텍스트 형태로 트리 규칙 출력
tree_rules = export_text(
    model,
    feature_names=iris.feature_names
)
print(tree_rules)

# 결과 예시:
# |--- petal length (cm) <= 2.45
# |   |--- class: setosa
# |--- petal length (cm) > 2.45
# |   |--- petal width (cm) <= 1.75
# |   |   |--- class: versicolor

김개발 씨는 트리 시각화에 익숙해졌습니다. 그런데 새로운 고민이 생겼습니다.

운영팀에서 "이 모델의 규칙을 우리 시스템에 직접 넣고 싶은데, 어떤 조건으로 분기하는지 텍스트로 정리해줄 수 있나요?"라고 요청한 것입니다. 박시니어 씨가 해결책을 알려주었습니다.

"export_text를 쓰면 깔끔하게 정리됩니다." export_text란 무엇일까요? 쉽게 비유하자면, export_text는 마치 레시피를 글로 적어주는 것과 같습니다.

요리 영상을 보면 이해는 되지만, 나중에 따라하려면 글로 적힌 레시피가 더 편합니다. 트리 시각화가 영상이라면, export_text는 글로 적힌 레시피입니다.

위의 코드를 살펴보겠습니다. export_text 함수는 매우 간단합니다.

학습된 모델과 특성 이름만 넣어주면 됩니다. 결과는 들여쓰기로 구조화된 텍스트입니다.

|--- 기호가 분기를 나타내고, 들여쓰기 깊이가 트리의 깊이를 나타냅니다. 출력 결과를 어떻게 읽어야 할까요?

맨 첫 줄 "petal length <= 2.45"가 루트 노드의 조건입니다. 그 아래 "class: setosa"는 조건이 참일 때의 예측입니다.

즉, 꽃잎 길이가 2.45cm 이하면 setosa로 분류됩니다. 다음 줄 "petal length > 2.45"는 조건이 거짓일 때의 분기입니다.

이런 식으로 쭉 따라가면 모든 규칙을 파악할 수 있습니다. 실무에서 이 기능은 어떻게 활용될까요?

가장 흔한 사용 사례는 규칙 기반 시스템 구현입니다. 머신러닝 모델을 서비스에 직접 배포하기 어려운 환경에서, 학습된 규칙을 if-else 문으로 변환해서 사용하기도 합니다.

export_text의 출력을 보고 그대로 코드로 옮기면 됩니다. 또 다른 활용 사례는 문서화입니다.

"이 모델은 어떤 로직으로 예측하나요?"라는 질문에 export_text 출력을 문서에 첨부하면 명확한 답변이 됩니다. 감사팀이나 규제 기관에서 모델 해석을 요구할 때도 유용합니다.

주의할 점이 있습니다. 트리가 깊고 복잡하면 텍스트 출력도 매우 길어집니다.

이런 경우에는 전체를 보기보다 특정 경로만 추출하는 것이 좋습니다. 또한 텍스트만으로는 전체 구조를 파악하기 어려우므로, 시각화와 함께 사용하는 것을 권장합니다.

김개발 씨가 출력 결과를 보며 감탄했습니다. "이거 복사해서 if-else로 바꾸면 바로 쓸 수 있겠네요!"

실전 팁

💡 - spacing 파라미터로 들여쓰기 간격을 조절할 수 있습니다

  • 출력 결과를 파일로 저장하려면 with open()과 함께 사용하세요

6. 특성 중요도 시각화

"선배, 트리는 이해했는데요. 어떤 특성이 제일 중요한지 한눈에 보여주는 차트는 없나요?" 김개발 씨가 물었습니다.

팀장님이 발표 자료에 넣을 시각적인 자료를 원했기 때문입니다. "물론 있죠.

막대 그래프로 깔끔하게 보여줄 수 있어요."

feature_importances_ 속성을 막대 그래프로 시각화하면 각 특성의 중요도를 직관적으로 비교할 수 있습니다. 어떤 특성이 예측에 가장 큰 영향을 미치는지 비개발자도 쉽게 이해할 수 있는 차트를 만들 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np

iris = load_iris()
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(iris.data, iris.target)

# 특성 중요도를 내림차순으로 정렬
importances = model.feature_importances_
indices = np.argsort(importances)[::-1]

# 막대 그래프 시각화
plt.figure(figsize=(10, 6))
plt.title("Feature Importances")
plt.bar(range(len(importances)), importances[indices])
plt.xticks(range(len(importances)),
           [iris.feature_names[i] for i in indices], rotation=45)
plt.tight_layout()
plt.savefig("feature_importance.png")

김개발 씨는 고객 이탈 예측 모델을 완성했습니다. 이제 팀장님께 보고할 차례였습니다.

그런데 팀장님은 기술적인 내용보다는 "그래서 뭐가 중요한 거야?"라는 질문에 답할 수 있는 자료를 원했습니다. 박시니어 씨가 조언했습니다.

"비개발자에게는 숫자보다 그림이 효과적이에요. 특성 중요도를 차트로 만들어보세요." 특성 중요도 시각화란 무엇일까요?

쉽게 비유하자면, 마치 팀 프로젝트에서 각 팀원의 기여도를 그래프로 보여주는 것과 같습니다. "철수가 40%, 영희가 35%, 민수가 25% 기여했습니다"라고 말하는 것보다 막대 그래프로 보여주면 훨씬 직관적입니다.

위의 코드를 자세히 살펴보겠습니다. **np.argsort(importances)[::-1]**이 핵심입니다.

이 코드는 중요도가 높은 순서대로 인덱스를 정렬합니다. 가장 중요한 특성이 왼쪽에, 덜 중요한 특성이 오른쪽에 오도록 만듭니다.

이렇게 하면 차트를 봤을 때 바로 "가장 중요한 건 이거구나"라고 파악할 수 있습니다. rotation=45는 x축 레이블을 45도 기울여서 겹치지 않게 합니다.

특성 이름이 길 때 유용합니다. "petal length (cm)"처럼 긴 이름도 깔끔하게 표시됩니다.

실무에서 이 차트는 매우 강력합니다. 경영진 보고에서 "고객 이탈에 가장 큰 영향을 미치는 요소는 최근 30일 접속 횟수입니다"라고 말하면서 이 차트를 보여주면 설득력이 확 올라갑니다.

데이터에 기반한 의사결정을 시각적으로 증명할 수 있기 때문입니다. 응용하면 더 다양한 인사이트를 얻을 수 있습니다.

예를 들어 중요도가 0에 가까운 특성이 있다면, 그 특성은 모델에 별로 기여하지 않는다는 의미입니다. 이런 특성은 제거해도 성능에 큰 영향이 없을 수 있습니다.

특성 선택에 활용할 수 있는 것입니다. 주의할 점이 있습니다.

특성 중요도는 해당 모델에서의 상대적 중요도입니다. 다른 모델을 사용하면 중요도 순위가 달라질 수 있습니다.

또한 상관관계가 높은 특성들은 중요도가 분산되어 실제보다 낮게 나올 수 있습니다. 김개발 씨가 차트를 팀장님께 보여드렸습니다.

"아, 이렇게 보니까 한눈에 들어오네. 좋아요!" 팀장님의 칭찬에 김개발 씨는 뿌듯함을 느꼈습니다.

실전 팁

💡 - 색상을 다르게 하면 더 보기 좋은 차트를 만들 수 있습니다 (plt.bar(..., color='steelblue'))

  • 중요도 값을 막대 위에 표시하면 정확한 수치도 함께 전달할 수 있습니다

7. 회귀 트리 시각화

"선배, 분류 말고 숫자를 예측하는 문제도 Decision Tree로 할 수 있나요?" 김개발 씨가 물었습니다. 이번에는 집값 예측 모델을 만들어야 했기 때문입니다.

"물론이죠. DecisionTreeRegressor를 사용하면 됩니다.

시각화 방법은 거의 같아요."

DecisionTreeRegressor는 연속적인 숫자를 예측하는 회귀 문제에 사용됩니다. 분류와 달리 리프 노드에서 클래스 대신 평균값을 반환합니다.

시각화 방법은 분류 트리와 동일하지만, 해석 방식이 조금 다릅니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeRegressor, plot_tree
from sklearn.datasets import fetch_california_housing
import matplotlib.pyplot as plt

housing = fetch_california_housing()
model = DecisionTreeRegressor(max_depth=3, random_state=42)
model.fit(housing.data, housing.target)

# 회귀 트리 시각화
plt.figure(figsize=(20, 10))
plot_tree(
    model,
    feature_names=housing.feature_names,
    filled=True,
    fontsize=9,
    precision=2     # 소수점 자릿수
)
plt.title("California Housing Price Prediction Tree")
plt.tight_layout()
plt.savefig("regression_tree.png")

김개발 씨는 부동산 팀으로부터 새로운 요청을 받았습니다. "주택 정보를 넣으면 예상 가격을 알려주는 모델을 만들어주세요." 분류가 아니라 가격이라는 숫자를 예측해야 하는 상황이었습니다.

박시니어 씨가 설명했습니다. "회귀 문제도 Decision Tree로 풀 수 있어요.

DecisionTreeRegressor를 쓰면 됩니다." 분류와 회귀의 차이점은 무엇일까요? 쉽게 비유하자면, 분류는 마치 "이 사과는 빨간색인가, 초록색인가?"를 맞히는 것이고, 회귀는 "이 사과의 무게는 몇 그램인가?"를 맞히는 것입니다.

분류는 정해진 카테고리 중 하나를 선택하고, 회귀는 연속적인 숫자를 예측합니다. 위의 코드를 살펴보겠습니다.

DecisionTreeRegressor는 DecisionTreeClassifier와 사용법이 거의 같습니다. fit으로 학습하고, predict로 예측합니다.

시각화도 plot_tree를 그대로 사용합니다. 다만 class_names 파라미터가 없습니다.

회귀에는 클래스가 없기 때문입니다. precision=2 파라미터가 새로 등장했습니다.

이 파라미터는 노드에 표시되는 숫자의 소수점 자릿수를 결정합니다. 집값처럼 숫자가 클 때는 소수점을 줄이는 것이 가독성에 좋습니다.

회귀 트리의 노드는 무엇을 보여줄까요? 분류 트리에서는 각 클래스의 샘플 수를 보여줬습니다.

회귀 트리에서는 **평균값(mean)**과 표준편차를 보여줍니다. 리프 노드의 value는 해당 노드에 도달한 샘플들의 평균 타겟값입니다.

새로운 데이터가 이 리프 노드에 도달하면 이 평균값이 예측값으로 반환됩니다. 실무에서 회귀 트리는 어디에 쓰일까요?

가격 예측, 수요 예측, 매출 예측 등 숫자를 예측하는 모든 문제에 활용됩니다. 특히 어떤 요인이 가격에 영향을 미치는지 해석해야 할 때 유용합니다.

"방 개수가 4개 이상이고 위치가 좋으면 평균 50만 달러입니다"라고 설명할 수 있습니다. 주의할 점이 있습니다.

회귀 트리는 학습 데이터의 범위를 벗어나는 예측을 하지 못합니다. 학습 데이터의 최대 집값이 100만 달러라면, 모델은 절대 100만 달러 이상을 예측하지 않습니다.

리프 노드의 평균값이 예측값이기 때문입니다. 김개발 씨가 시각화된 트리를 보며 말했습니다.

"분류랑 거의 같네요! 다만 클래스 대신 숫자가 나오는 거군요."

실전 팁

💡 - 회귀 트리는 이상치에 민감할 수 있으니 데이터 전처리에 신경 쓰세요

  • 실제로는 단일 트리보다 Random Forest나 Gradient Boosting이 더 좋은 성능을 냅니다

8. 하이퍼파라미터 튜닝 효과 시각화

"max_depth를 바꾸면 트리가 어떻게 달라지나요?" 김개발 씨가 궁금해했습니다. 숫자만 바꿔서는 감이 오지 않았습니다.

박시니어 씨가 제안했습니다. "여러 깊이의 트리를 한 번에 그려보면 차이가 확 보여요."

max_depth는 트리의 최대 깊이를 제한하는 가장 중요한 하이퍼파라미터입니다. 깊이가 달라지면 트리의 복잡도와 예측 성능이 크게 변합니다.

여러 깊이의 트리를 나란히 시각화하면 적절한 깊이를 직관적으로 선택할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

iris = load_iris()

# 여러 깊이의 트리를 비교 시각화
fig, axes = plt.subplots(1, 3, figsize=(24, 8))
depths = [2, 3, 4]

for ax, depth in zip(axes, depths):
    model = DecisionTreeClassifier(max_depth=depth, random_state=42)
    model.fit(iris.data, iris.target)

    plot_tree(model, feature_names=iris.feature_names,
              class_names=iris.target_names, filled=True, ax=ax)
    ax.set_title(f"max_depth = {depth}")

plt.tight_layout()
plt.savefig("depth_comparison.png", dpi=150)

김개발 씨는 max_depth를 어떻게 설정해야 할지 고민이었습니다. 너무 작으면 제대로 학습이 안 될 것 같고, 너무 크면 과적합이 걱정되었습니다.

"적당한" 값이 뭔지 감이 오지 않았습니다. 박시니어 씨가 새로운 접근법을 제시했습니다.

"백문이 불여일견이에요. 여러 깊이로 트리를 그려서 비교해보세요." max_depth가 왜 중요할까요?

쉽게 비유하자면, max_depth는 마치 인터뷰 질문 횟수와 같습니다. 질문을 2번만 하면 대략적인 판단만 가능합니다.

10번 하면 매우 정확하지만, 그 사람에게만 맞춤화된 판단이 되어 다른 사람에게는 적용하기 어렵습니다. 적절한 질문 횟수를 찾는 것이 핵심입니다.

위의 코드를 살펴보겠습니다. **plt.subplots(1, 3, ...)**으로 가로로 3개의 차트를 배치합니다.

**zip(axes, depths)**를 사용해서 각 차트에 다른 깊이의 트리를 그립니다. 이렇게 하면 한 화면에서 깊이별 트리를 비교할 수 있습니다.

시각화 결과에서 무엇을 볼 수 있을까요? depth=2인 트리는 매우 단순합니다.

노드가 몇 개 없어서 대략적인 분류만 가능합니다. depth=3이 되면 조금 더 세분화됩니다.

depth=4가 되면 노드가 많아지고 복잡해집니다. 이렇게 눈으로 비교하면 "아, depth=3 정도가 적당하겠구나"라는 감이 옵니다.

실무에서는 시각화와 함께 성능 지표도 확인합니다. 학습 데이터 정확도와 검증 데이터 정확도를 함께 그래프로 그리면 과적합 지점을 찾을 수 있습니다.

학습 정확도는 계속 올라가는데 검증 정확도가 떨어지기 시작하면, 그 지점이 과적합의 시작입니다. 이 방식은 다른 하이퍼파라미터에도 적용할 수 있습니다.

min_samples_split, min_samples_leaf 등의 파라미터도 같은 방식으로 비교할 수 있습니다. 여러 조합을 시각화해서 비교하면 최적의 설정을 찾는 데 도움이 됩니다.

주의할 점이 있습니다. 깊이가 깊어질수록 노드가 기하급수적으로 늘어납니다.

depth=5만 되어도 최대 32개의 리프 노드가 생길 수 있습니다. 시각화가 복잡해지므로 비교용으로는 낮은 깊이를 사용하는 것이 좋습니다.

김개발 씨가 세 개의 트리를 비교하며 말했습니다. "이렇게 보니까 확실히 차이가 느껴지네요.

depth=3이 적당해 보여요!"

실전 팁

💡 - GridSearchCV와 함께 사용하면 최적의 하이퍼파라미터를 자동으로 찾을 수 있습니다

  • 시각화는 이해를 위한 것이고, 최종 선택은 검증 데이터 성능으로 판단하세요

9. 결정 경계 시각화

"트리가 실제로 데이터를 어떻게 나누는지 보고 싶어요." 김개발 씨가 말했습니다. 노드와 분기 조건은 이해했지만, 전체적인 그림이 그려지지 않았습니다.

박시니어 씨가 웃었습니다. "결정 경계를 시각화하면 됩니다.

트리가 공간을 어떻게 분할하는지 한눈에 볼 수 있어요."

결정 경계는 모델이 각 클래스를 구분하는 경계선입니다. Decision Tree는 특성 축에 수직인 직선으로 공간을 분할합니다.

2차원 데이터로 결정 경계를 시각화하면 트리의 분류 방식을 직관적으로 이해할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np

iris = load_iris()
X = iris.data[:, 2:4]   # 꽃잎 길이, 너비만 사용
y = iris.target

model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X, y)

# 결정 경계를 위한 메시 그리드 생성
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                      np.linspace(y_min, y_max, 200))

# 예측 및 시각화
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
plt.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis', edgecolor='black')
plt.xlabel(iris.feature_names[2])
plt.ylabel(iris.feature_names[3])
plt.savefig("decision_boundary.png")

김개발 씨는 트리의 노드와 분기 조건을 이해했습니다. 하지만 "petal length <= 2.45"라는 조건이 실제 데이터 공간에서 어떤 의미인지 직관적으로 와닿지 않았습니다.

박시니어 씨가 새로운 시각화를 보여주었습니다. "결정 경계를 그려보면 트리가 데이터를 어떻게 나누는지 바로 이해할 수 있어요." 결정 경계란 무엇일까요?

쉽게 비유하자면, 결정 경계는 마치 국경선과 같습니다. 지도에서 국경선을 기준으로 이쪽은 A나라, 저쪽은 B나라라고 구분하듯이, 결정 경계를 기준으로 이쪽은 클래스 A, 저쪽은 클래스 B로 분류합니다.

위의 코드를 자세히 살펴보겠습니다. 먼저 2개의 특성만 선택합니다.

시각화는 2차원 평면에서 이루어지므로, 4개의 특성 중 꽃잎 길이와 너비만 사용합니다. np.meshgrid로 2차원 공간을 촘촘한 격자로 나눕니다.

각 격자점에서 모델이 예측하는 클래스를 계산합니다. contourf로 같은 클래스로 예측되는 영역을 색칠합니다.

시각화 결과에서 무엇을 볼 수 있을까요? Decision Tree의 결정 경계는 항상 축에 수직인 직선입니다.

대각선이나 곡선이 아닙니다. 이는 트리가 "petal length <= 2.45"처럼 한 번에 하나의 특성만 기준으로 분기하기 때문입니다.

결과적으로 공간이 직사각형 영역들로 분할됩니다. 이 특성이 Decision Tree의 한계이기도 합니다.

만약 두 클래스가 대각선으로 구분된다면, Decision Tree는 계단 모양의 경계를 만들어서 근사합니다. 이런 경우에는 다른 알고리즘이 더 효과적일 수 있습니다.

실무에서 결정 경계 시각화는 어떻게 활용될까요? 모델이 어떻게 동작하는지 설명할 때 매우 유용합니다.

"보시다시피 접속 횟수가 5회 미만이고 구매액이 1만원 미만인 영역은 이탈 위험군으로 분류됩니다"라고 그림을 보여주며 설명할 수 있습니다. 주의할 점이 있습니다.

실제 데이터는 대부분 2차원보다 훨씬 많은 특성을 가집니다. 결정 경계 시각화는 2개 특성만 선택해서 보여주는 것이므로, 전체 모델의 동작을 완전히 대표하지는 않습니다.

가장 중요한 2개 특성을 선택하는 것이 좋습니다. 김개발 씨가 결정 경계 그림을 보며 감탄했습니다.

"아, 이래서 트리가 사각형으로 나누는 거구나! 드디어 전체 그림이 그려지네요."

실전 팁

💡 - 중요도가 높은 두 특성을 선택하면 더 의미 있는 시각화가 됩니다

  • alpha 값을 조절해서 배경 색상의 투명도를 조절할 수 있습니다

10. 과적합과 가지치기 시각화

김개발 씨가 만든 모델이 학습 데이터에서는 100% 정확도를 보였습니다. 하지만 새로운 데이터에서는 성능이 형편없었습니다.

"왜 이런 거죠?" 당황한 김개발 씨에게 박시니어 씨가 말했습니다. "전형적인 과적합이에요.

가지치기가 필요합니다."

과적합은 모델이 학습 데이터에 너무 맞춰져서 새로운 데이터에서는 성능이 떨어지는 현상입니다. Decision Tree에서는 가지치기를 통해 트리의 복잡도를 줄여 과적합을 방지합니다.

가지치기 전후의 트리를 비교 시각화하면 그 효과를 명확히 볼 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42)

# 과적합 모델 vs 가지치기 모델
fig, axes = plt.subplots(1, 2, figsize=(20, 8))

# 과적합 모델 (깊이 제한 없음)
overfit = DecisionTreeClassifier(random_state=42)
overfit.fit(X_train, y_train)
print(f"과적합 - 학습: {overfit.score(X_train, y_train):.2f}")
print(f"과적합 - 테스트: {overfit.score(X_test, y_test):.2f}")

# 가지치기 모델
pruned = DecisionTreeClassifier(max_depth=3, min_samples_leaf=5)
pruned.fit(X_train, y_train)
print(f"가지치기 - 테스트: {pruned.score(X_test, y_test):.2f}")

김개발 씨는 자신만만하게 모델을 팀장님께 보여드렸습니다. "학습 정확도 100%입니다!" 하지만 팀장님이 새로운 데이터로 테스트해보자 70%도 안 나왔습니다.

김개발 씨는 당황했습니다. 박시니어 씨가 설명했습니다.

"이게 바로 과적합이에요. 시험 문제를 달달 외웠는데, 조금만 다른 문제가 나오면 못 푸는 것과 같아요." 과적합이란 정확히 무엇일까요?

쉽게 비유하자면, 과적합은 마치 시험 족보만 외운 학생과 같습니다. 족보와 똑같은 문제가 나오면 100점이지만, 조금만 변형되면 손도 못 댑니다.

모델이 학습 데이터의 패턴뿐만 아니라 노이즈까지 외워버린 것입니다. 위의 코드를 살펴보겠습니다.

첫 번째 모델은 max_depth 제한 없이 학습합니다. 트리가 아주 깊어져서 모든 학습 데이터를 완벽하게 분류합니다.

하지만 테스트 데이터에서는 성능이 떨어집니다. 두 번째 모델은 max_depth=3, min_samples_leaf=5로 제한합니다.

이렇게 하면 트리가 단순해지고, 새로운 데이터에도 잘 동작합니다. 가지치기에는 어떤 방법들이 있을까요?

max_depth는 트리의 최대 깊이를 제한합니다. min_samples_split은 노드를 분할하기 위해 필요한 최소 샘플 수입니다.

min_samples_leaf는 리프 노드에 있어야 하는 최소 샘플 수입니다. ccp_alpha는 비용 복잡도 가지치기의 강도를 조절합니다.

각 파라미터가 트리에 미치는 영향을 시각화해보면 좋습니다. 가지치기 전의 트리는 매우 깊고 복잡합니다.

리프 노드가 많고, 각 노드에 샘플이 하나씩만 있을 수도 있습니다. 가지치기 후의 트리는 훨씬 단순합니다.

노드가 적고, 각 노드에 충분한 샘플이 있습니다. 실무에서 적절한 가지치기 수준을 찾는 방법이 있습니다.

교차 검증을 사용하는 것입니다. 학습 데이터를 여러 번 나눠서 테스트하고, 평균 성능이 가장 좋은 파라미터를 선택합니다.

GridSearchCV를 사용하면 자동으로 최적의 조합을 찾을 수 있습니다. 주의할 점이 있습니다.

가지치기를 너무 많이 하면 과소적합이 발생합니다. 모델이 너무 단순해서 데이터의 패턴을 제대로 학습하지 못하는 것입니다.

과적합과 과소적합 사이의 균형점을 찾는 것이 핵심입니다. 김개발 씨가 두 트리를 비교하며 이해했습니다.

"아, 너무 복잡하면 안 되는 거구나. 적당히 단순해야 새로운 데이터도 잘 맞히는군요!"

실전 팁

💡 - 학습 정확도와 테스트 정확도의 차이가 크면 과적합을 의심하세요

  • ccp_alpha를 점진적으로 높여가며 적절한 복잡도를 찾을 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#DecisionTree#MachineLearning#Visualization#Scikit-learn#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.