|
2753 | 2753 | }, |
2754 | 2754 | { |
2755 | 2755 | "cell_type": "markdown", |
2756 | | - "id": "41d39ca3-31c0-41dd-a19c-da009338005f", |
2757 | 2756 | "metadata": {}, |
2758 | 2757 | "source": [ |
2759 | | - ":::{warning}\n", |
2760 | | - "\n", |
2761 | | - "여기서 사용하는 모델이 전처리와 함게 하나의 파이프라인으로 묶여 있음에 주의한다.\n", |
| 2758 | + "특성 중요도는 전처리 변환기와 아무 상관이 없으며, 랜덤 포레스트 모델 등 일부 모델에서만 훈련중에 함께 저장된다.\n", |
2762 | 2759 | "\n", |
| 2760 | + "아래 코드는 `final_model`에서 랜덤 포레스트 모델의 이름이 `\"random_forest\"`로 지정되었기에 해당 이름을 이용하여 인덱싱 한 다음에 특성 중요도를 확인한다." |
| 2761 | + ] |
| 2762 | + }, |
| 2763 | + { |
| 2764 | + "cell_type": "markdown", |
| 2765 | + "metadata": {}, |
| 2766 | + "source": [ |
2763 | 2767 | "```python\n", |
2764 | | - "full_pipeline = Pipeline([\n", |
2765 | | - " (\"preprocessing\", preprocessing),\n", |
2766 | | - " (\"random_forest\", RandomForestRegressor(random_state=42)),\n", |
2767 | | - "])\n", |
2768 | | - "\n", |
2769 | | - "특성 중요도와 같이 모델이 예측 훈련을 하는 동안 학습한 데이터는\n", |
2770 | | - "변환기가 아닌 예측기에 저장된다.\n", |
2771 | | - "즉, 위 파이프라인에서 `\"random_forest\"` 이름이 가리키는 `RandomForestRegressor`\n", |
2772 | | - "예측기에 저장된다.\n", |
| 2768 | + "final_model[\"random_forest\"].feature_importances_\n", |
2773 | 2769 | "```" |
2774 | 2770 | ] |
2775 | 2771 | }, |
|
2850 | 2846 | "id": "80202587-a23e-4690-9f5a-a298c72e5f19", |
2851 | 2847 | "metadata": {}, |
2852 | 2848 | "source": [ |
2853 | | - "(2) 결측치 처리\n", |
| 2849 | + "(2) `total_bedrooms`에 포함된 결측치를 다르게 처리할 경우 모델의 성능에 차이가 발생하는지 여부룰 확인하라. \n", |
2854 | 2850 | "\n", |
2855 | | - "- 옵션 1: 결측치를 포함하는 구역 샘플 삭제\n", |
| 2851 | + "가. 결측치를 포함하는 구역 샘플 삭제\n", |
2856 | 2852 | "\n", |
2857 | | - " ```python\n", |
2858 | | - " housing_option1 = housing.copy()\n", |
2859 | | - " housing_option1.dropna(subset=[\"total_bedrooms\"], inplace=True)\n", |
2860 | | - " ```\n", |
| 2853 | + "```python\n", |
| 2854 | + "housing.dropna(subset=[\"total_bedrooms\"], inplace=True)\n", |
| 2855 | + "```\n", |
2861 | 2856 | "\n", |
2862 | | - "- 옵션 2: 결측치가 포함된 특성 삭제\n", |
| 2857 | + "나. 결측치가 포함된 특성 자체 제거\n", |
2863 | 2858 | "\n", |
2864 | | - " ```python\n", |
2865 | | - " housing_option2 = housing.copy()\n", |
2866 | | - " housing_option2.drop(\"total_bedrooms\", axis=1, inplace=True)\n", |
2867 | | - " ```" |
| 2859 | + "```python\n", |
| 2860 | + "housing.drop(\"total_bedrooms\", axis=1, inplace=True)\n", |
| 2861 | + "```" |
2868 | 2862 | ] |
2869 | 2863 | }, |
2870 | 2864 | { |
2871 | 2865 | "cell_type": "markdown", |
2872 | 2866 | "metadata": {}, |
2873 | 2867 | "source": [ |
2874 | | - "(3) OrdinalEncoder 활용\n", |
| 2868 | + "(3) 해안 근접도 특성에 대해 원-핫 인코딩 대신 `OrdinalEncoder` 변환기 객체를 적용할 경우 훈련된 모델의 성능이 어떻게 달라지며, 성능의 차이에 대한 근거를 추정하라.\n", |
2875 | 2869 | "\n", |
2876 | 2870 | "```python\n", |
2877 | 2871 | "housing_cat = housing[[\"ocean_proximity\"]]\n", |
| 2872 | + "\n", |
2878 | 2873 | "from sklearn.preprocessing import OrdinalEncoder\n", |
2879 | 2874 | "ordinal_encoder = OrdinalEncoder()\n", |
2880 | 2875 | "housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)\n", |
|
2885 | 2880 | "cell_type": "markdown", |
2886 | 2881 | "metadata": {}, |
2887 | 2882 | "source": [ |
2888 | | - "(4) `preprocessing`과 예측기 분리.\n", |
| 2883 | + "(4) 아래 코드는 `preprocessing` 변환기 파이프라인과 랜덤 포레스트 회귀 모델을 하나로 묶은 모델을 지정한다.\n", |
2889 | 2884 | "\n", |
2890 | | - "- `preprocessing`을 이용하여 먼저 데이터셋 변환\n", |
2891 | | - "- 변환된 데이터셋을 이용하여 모델 훈련" |
2892 | | - ] |
2893 | | - }, |
2894 | | - { |
2895 | | - "cell_type": "markdown", |
2896 | | - "metadata": {}, |
2897 | | - "source": [ |
2898 | | - ":::{note} 변환기 포함 파이프라인 모델의 장단점\n", |
| 2885 | + "```python\n", |
| 2886 | + "full_pipeline = Pipeline([\n", |
| 2887 | + " (\"preprocessing\", preprocessing),\n", |
| 2888 | + " (\"random_forest\", RandomForestRegressor(random_state=42)),\n", |
| 2889 | + "])\n", |
| 2890 | + "```\n", |
2899 | 2891 | "\n", |
2900 | 2892 | "전처리 변환기와 예측기 모델을 하나로 묶은 파이프라인 머신러닝 모델의 장점은 다음과 같다.\n", |
2901 | 2893 | "\n", |
2902 | 2894 | "- 코드 간소화: 전처리와 훈련 과정을 하나의 객체로 묶어 코드가 깔끔해지고 관리가 쉬워짐.\n", |
2903 | 2895 | "- 데이터 누설 방지: 교차 검증 시 각 폴드에 맞게 전처리가 독립적으로 수행되어 검증 데이터가 훈련에 반영되는 것을 막음\n", |
2904 | 2896 | "- 모델 미세 조정 용이: 예측기뿐만 아니라 변환기의 특성까지 한 번에 고려하며 교차 검증 진행 가능 가능\n", |
2905 | 2897 | "\n", |
2906 | | - "반면에 단점으로 중간 단계의 변환 결과를 즉각적으로 확인하기 어려워 디버깅이 까다롭다는 점과, 그리드 탐색 시 전처리 과정이 매번 중복 수행되어 연산 시간(비용)이 다소 증가할 수 있다는 점을 고려해야 한다.\n", |
2907 | | - ":::" |
| 2898 | + "반면에 몇 가지 단점을 갖는다.\n", |
| 2899 | + "\n", |
| 2900 | + "1. 중간 전처리 결과(데이터)를 직접 눈으로 확인하기 어려움\n", |
| 2901 | + "\n", |
| 2902 | + " - 가장 큰 단점임.\n", |
| 2903 | + " - 원본 데이터가 파이프라인의 `fit_predict`를 통과하면 바로 최종 예측값이 나와버리기 때문에, 중간에 원-핫 인코딩이 잘 되었는지, 스케일링은 정상적으로 되었는지 중간 데이터프레임 형태를 디버깅하거나 시각화하기가 까다로움\n", |
| 2904 | + " - 이를 보려면 결국 파이프라인을 쪼개서 `transform`만 따로 실행해봐야 함.\n", |
| 2905 | + "\n", |
| 2906 | + "2. 하이퍼파라미터 튜닝 시 이름이 길고 복잡해짐\n", |
| 2907 | + "\n", |
| 2908 | + " - `GridSearchCV` 등에서 모델 미세 조정할 때, 파라미터 이름을 지정하는 방식이 매우 길어짐.\n", |
| 2909 | + " - (예: 모델만 있을 때) `max_features`\n", |
| 2910 | + " - (파이프라인 결합 시) `random_forest__max_features`, `preprocessing__geo__n_clusters` 같이 `단계이름__파라미터이름` 형태의 이중 밑줄(`__`) 문법을 사용해야 해서 코드가 길어지고 오타가 나기 쉬움.\n", |
| 2911 | + "\n", |
| 2912 | + "3. 매우 큰 데이터셋에서 튜닝 시 반복 연산(시간 효율성 저하)\n", |
| 2913 | + "\n", |
| 2914 | + " - 교차 검증이나 그리드 서치를 돌릴 때, 파이프라인 전체를 통째로 넣으면 모델의 파라미터만 바꾸면서 실험할 때도 무거운 앞단 전처리(예: KMeans 클러스터링, 특성 추가 등)를 반복해서 매번 다시 계산하게 될 수 있음.\n", |
| 2915 | + " - 단, 사이킷런 파이프라인의 `memory` 인자를 사용하여 중간 변환 결과를 캐싱(저장)하면 이 문제는 해결할 수 있음.\n", |
| 2916 | + "\n", |
| 2917 | + "4. 예측 결과 분석(변수 중요도 등) 추출의 번거로움\n", |
| 2918 | + "\n", |
| 2919 | + " - `RandomForest`나 `XGBoost` 같은 트리를 학습시킨 후 \"어떤 특성이 가장 중요했지?\"(`feature_importances_`)를 물어보려면, 모델을 파이프라인 껍질 안에서 꺼내와야 함.\n", |
| 2920 | + " - 더불어, 전처리 과정에서 새로 생겨난 컬럼 이름들(더미 변수 등)까지 매칭시켜주려면 `pipeline.named_steps['전처리'].get_feature_names_out()` 같은 복잡한 코드를 추가로 작성해야 함.\n", |
| 2921 | + "\n", |
| 2922 | + "이제 `preprocessing`과 예측기를 분리하여 훈련을 진행하는 코드를 작성하여,\n", |
| 2923 | + "앞서 언급한 `full_pipeline`의 장단점을 확인하라." |
2908 | 2924 | ] |
2909 | 2925 | } |
2910 | 2926 | ], |
|
0 commit comments