{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Data Analysis**\n", "**머신러닝 Work Flow**\n", "1. 데이터 준비\n", "1. **Train / Test** Data Set\n", "1. **알고리즘의** 학습/ 평가/ 선정\n", "1. 배포 및 모니터링" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# **1 데이터 준비 / 전처리**\n", "1. **프로젝트의 목표를** 설정 / 이해하기\n", "1. **관련된 모든 Field 데이터를** 수집하기\n", "1. **Field 별** 데이터를 명확히 정의하고 **포맷의 일관성 유지**\n", "1. 결측 데이터 처리하기 (결측 데이터 대체를 하거나, 해당 필드를 삭제한다)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **01 결측치 데이터 보간법**\n", "데이터 필드에 **결측치 값 np.NaN** 존재하는 경우" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **1) sklearn의 Imputer() 사용**\n", "결측치를 보완하는 인스턴스 객체를 활용" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[30, 100], [20, 50], [35, nan], [25, 80], [30, 70], [40, 60]]" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "data_origin = [[30, 100], [20, 50], [35, np.nan],\n", " [25, 80], [30, 70], [40, 60]]\n", "data_origin" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[30, nan], [20, 50], [35, nan], [25, 80], [30, nan], [40, 60]]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "data_origin = [[30, np.nan], [20, 50], [35, np.nan],\n", " [25, 80], [30, np.nan], [40, 60]]\n", "data_origin" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[30. 63.33333333]\n", " [20. 50. ]\n", " [35. 63.33333333]\n", " [25. 80. ]\n", " [30. 63.33333333]\n", " [40. 60. ]]\n" ] } ], "source": [ "# 보간법 1 : 평균값으로 결측값을 대체\n", "from sklearn.preprocessing import Imputer\n", "imp_mean = Imputer(missing_values='NaN', strategy='mean')\n", "imp_mean.fit(data_origin)\n", "\n", "data_mean_imp = imp_mean.transform(data_origin)\n", "print(data_mean_imp)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[30. 60.]\n", " [20. 50.]\n", " [35. 60.]\n", " [25. 80.]\n", " [30. 60.]\n", " [40. 60.]]\n" ] } ], "source": [ "# 보간법 2 : 중간값으로 결측값을 대체\n", "imp_median = Imputer(missing_values='NaN', strategy='median')\n", "imp_median.fit(data_origin)\n", "\n", "data_median_imp = imp_median.transform(data_origin)\n", "print(data_median_imp)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[20. 63.33333333]\n", " [30. 63.33333333]\n", " [30. 70. ]\n", " [30. 63.33333333]]\n" ] } ], "source": [ "# 보간법 3 : 새로운 데이터에 보간법 적용\n", "new = [[20, np.nan],\n", " [30, np.nan],\n", " [np.nan, 70],\n", " [np.nan, np.nan]]\n", "\n", "new_mean_imp = imp_mean.transform(new)\n", "print(new_mean_imp)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **2) 결측 데이터 모델링 성능비교**\n", "1. **sklearn의 당뇨질환 데이터를** 활용하여 **결측데이터** 성능비교\n", "1. **Random Forest 모델의** $R^2$ 성능비교" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "전체 데이터 Index : 442\n", "결측 데이터 Index : 110\n" ] } ], "source": [ "# 불완전한 당뇨질환 데이터를 불러온다\n", "from sklearn import datasets\n", "dataset = datasets.load_diabetes()\n", "X_full, y = dataset.data, dataset.target\n", "\n", "# 25% 의 결측치를 OverWriting 한다\n", "m, n = X_full.shape\n", "m_missing = int(m * 0.25)\n", "print(\"전체 데이터 Index : {:,}\\n결측 데이터 Index : {:,}\".format(m, m_missing))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# m_missing 갯수의 데이터를 무작위로 추출한다\n", "np.random.seed(42)\n", "missing_samples = np.array([True] * m_missing + [False] * (m - m_missing))\n", "np.random.shuffle(missing_samples)\n", "\n", "# 결측 데이터에 대해 1개의 feature를 무작위로 선택한다\n", "missing_features = np.random.randint(low=0, high=n, size=m_missing)\n", "# nan 로 결측값을 변경한다\n", "X_missing = X_full.copy()\n", "X_missing[np.where(missing_samples)[0], missing_features] = np.nan\n", "\n", "# 결측값이 포함된 샘플은 제외한다\n", "X_rm_missing = X_missing[~missing_samples, :]\n", "y_rm_missing = y[~missing_samples]" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/markbaum/Python/python/lib/python3.6/site-packages/sklearn/ensemble/weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an internal NumPy module and should not be imported. It will be removed in a future NumPy release.\n", " from numpy.core.umath_tests import inner1d\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "결측값이 제거된 DataSet 의 R^2 : 0.39\n" ] } ], "source": [ "# 결측값이 제거된 DataSet의 R^2 추정치를 계산한다\n", "from sklearn.ensemble import RandomForestRegressor\n", "from sklearn.model_selection import cross_val_score\n", "regressor = RandomForestRegressor(random_state = 42, \n", " max_depth = 10, \n", " n_estimators = 100)\n", "score_rm_missing = cross_val_score(\n", " regressor, X_rm_missing, y_rm_missing).mean()\n", "print('결측값이 제거된 DataSet 의 R^2 : {0:.2f}'.format(score_rm_missing))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **3) 결측 데이터 보간시 모델링 성능비교**\n", "1. **sklearn의 당뇨질환 데이터를** 활용하여 **결측데이터** 를 **평균값으로 보간**\n", "1. **Random Forest 모델의** $R^2$ 성능비교" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Score with the data set with missing values replaced by mean: 0.42\n" ] } ], "source": [ "# Imputation with mean value\n", "imp_mean = Imputer(missing_values='NaN', strategy='mean')\n", "X_mean_imp = imp_mean.fit_transform(X_missing)\n", "# Estimate R^2 on the data set with missing samples removed\n", "regressor = RandomForestRegressor(random_state=42, max_depth=10, n_estimators=100)\n", "score_mean_imp = cross_val_score(regressor, X_mean_imp, y).mean()\n", "print('Score with the data set with missing values replaced by mean: {0:.2f}'.format(score_mean_imp))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **4) 원본대비 결측/보간시 모델링 성능비교**\n", "1. 원본의 $R^2$ 값이 위의 데이터 성능과 비교시 차이가 없다.(원본의 정보력이 약함)\n", "1. 데이터별 상대적으로 비교하여 적합한 방법을 찾는다" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Score with the full data set: 0.44\n" ] } ], "source": [ "# Estimate R^2 on the full data set\n", "regressor = RandomForestRegressor(random_state = 42, \n", " max_depth = 10, \n", " n_estimators = 500)\n", "score_full = cross_val_score(regressor, X_full, y).mean()\n", "print('Score with the full data set: {0:.2f}'.format(score_full))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **5) 학습 데이터 Set 생성시 참고사항**\n", "위의 단계에서 데이터Set 을 잘 준비했다면 \n", "1. **데이터 전처리** : Feature Encoding, Feature Scaling, Feature Selection, 차원축소 등\n", "1. Feature Engineering (**파생변수** 생성하기)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# **2 Train / Test 데이터 Set**\n", "1. 위의 내용을 바탕으로 **원본 데이터를 일정한 포맷으로** 정리\n", "1. **학습 알고리즘에 용이한** 형태로 **데이터를 변형/ 전처리 작업을** 진행한다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **01 Feature Engineering (범주형 데이터)**\n", "1. **도메인 전문성**을 이용하여 **다양한 Feature Enginering** 을 진행\n", "1. 수치형 데이터를 **범주형 Feature로** 변환 : 범주형/ 연속형 구분은 **숫자가 수학적인 의미 제공여부**로 구분\n", "1. 범주형 Feature로 인코딩시 주의할 점 (ex> **Naive Bayse**)\n", " 1. **Naive Bayse** 또는 **Decision Tree** 알고리즘의 경우 **범주형 데이터만** 학습이 가능\n", " 1. **해당 알고리즘에** 적합한 형태로 데이터를 맞춘다\n", " 1. 범주형은 **컬럼별 압축** 하므로, **Feature 간 유사도가 적어도** 모델생성에는 영향이 없다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **02 Feature Selection**\n", "데이터중 **중요한 일부를 추출하는 과정** 으로, 모든경우 정확도를 높이진 않는다\n", "1. 학습에 **불필요한 feature들을 제거** 한다\n", "1. 이로 인해서 **Over Fitting 문제를** 줄인다\n", "1. **모델의 성능이** 높아진다\n", "1. 결과를 판단하여 **Feature Selection 모델에 적용 여부를** 결정" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **1) 숫자로 작성한 데이터 Set 불러온다**\n", "sklearn 데이터 불러오기" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1797, 64)\n" ] } ], "source": [ "# 손으로 작성한 DataSet을 불러온다\n", "from sklearn.datasets import load_digits\n", "dataset = load_digits()\n", "X, y = dataset.data, dataset.target\n", "print(X.shape)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Score with the original data set: 0.88\n" ] } ], "source": [ "# 64차원 원본 데이터의 정확도를 측정한다\n", "from sklearn.svm import SVC\n", "from sklearn.model_selection import cross_val_score\n", "classifier = SVC(gamma=0.005)\n", "score = cross_val_score(classifier, X, y).mean()\n", "print('Score with the original data set: {0:.2f}'.format(score))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **2) Feature Selection 1 : Random Forest**\n", "**Random Forest** 로 작업 후, **중요도 스코어를 기준으로 정렬한다**" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Score with the data set of top 10 features: 0.84\n", "Score with the data set of top 15 features: 0.93\n", "Score with the data set of top 25 features: 0.94\n", "Score with the data set of top 35 features: 0.92\n", "Score with the data set of top 45 features: 0.88\n" ] } ], "source": [ "# Feature selection with random forest\n", "from sklearn.ensemble import RandomForestClassifier\n", "random_forest = RandomForestClassifier(n_estimators=100, criterion='gini', n_jobs=-1)\n", "random_forest.fit(X, y)\n", "\n", "# Sort features based on their importancies\n", "feature_sorted = np.argsort(random_forest.feature_importances_)\n", "\n", "# Select different number of top features\n", "K = [10, 15, 25, 35, 45]\n", "for k in K:\n", " top_K_features = feature_sorted[-k:]\n", " X_k_selected = X[:, top_K_features]\n", " # Estimate accuracy on the data set with k selected features\n", " classifier = SVC(gamma=0.005)\n", " score_k_features = cross_val_score(classifier, X_k_selected, y).mean()\n", " print('Score with the data set of top {0} features: {1:.2f}'.format(k, score_k_features))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **03 PCA : 차원축소**\n", "차원 축소는 Feature Selection과 유사한 장점이 존재한다\n", "1. 불필요한 feature와 **상관관계가 있는 feature들을 하나로** 합친다\n", "1. 이로인해 Overfitting 문제를 줄여준다\n", "1. 상관성이 낮은 feature 로 예측모델의 학습을 진행할 수록 일반화 성능이 향상된다" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Score with the data set of top 10 components: 0.95\n", "Score with the data set of top 15 components: 0.95\n", "Score with the data set of top 25 components: 0.91\n", "Score with the data set of top 35 components: 0.89\n", "Score with the data set of top 45 components: 0.88\n" ] } ], "source": [ "from sklearn.decomposition import PCA\n", "\n", "N = [10, 15, 25, 35, 45]\n", "for n in N:\n", " pca = PCA(n_components=n)\n", " X_n_kept = pca.fit_transform(X)\n", " # Estimate accuracy on the data set with top n components\n", " classifier = SVC(gamma=0.005)\n", " score_n_components = cross_val_score(classifier, X_n_kept, y).mean()\n", " print('Score with the data set of top {0} components: {1:.2f}'.format(n, score_n_components))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **04 전문성이 부족한 경우 Feature Enginnering**\n", "Feature Enginnering 방법중 **일반화 내용들을** 살펴보자\n", "1. **이진화** : 수치형 feature를 임계치 기준 **boolean 형식으로** 변환\n", "1. **이산화** : 수치형 feature를 특정한 **몇가지 범주형 feature로** 변환\n", "1. **상호작용** : 합, 곱 등이 포함된 2개의 **범주형 feature 결합조건이** 해당\n", "1. **다항변환** : **다항 대화형 피쳐를** 생성" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1]\n", " [0]\n", " [1]\n", " [0]]\n" ] } ], "source": [ "from sklearn.preprocessing import Binarizer\n", "\n", "X = [[4], [1], [3], [0]]\n", "binarizer = Binarizer(threshold=2.9)\n", "X_new = binarizer.fit_transform(X)\n", "print(X_new)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 1. 2. 4. 4. 8. 16.]\n", " [ 1. 1. 3. 1. 3. 9.]\n", " [ 1. 3. 2. 9. 6. 4.]\n", " [ 1. 0. 3. 0. 0. 9.]]\n" ] } ], "source": [ "from sklearn.preprocessing import PolynomialFeatures\n", "\n", "X = [[2, 4], [1, 3], [3, 2], [0, 3]]\n", "poly = PolynomialFeatures(degree=2)\n", "X_new = poly.fit_transform(X)\n", "print(X_new)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **05 각 피쳐 생성과정 문서화**\n", "1. 각 feature를 **어떤 기준으로 생성했는지 문서로** 남겨야 한다\n", "1. **어떤 기준으로** 생성했는지를 명확하게 파악 가능해야 한다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# **3 알고리즘의 학습 / 평가**\n", "알고리즘에 적합한 튜닝 작업에 소요되는 시간이 많으므로, 아래 중 1~3개를 택해서 시도한다\n", "1. Train 데이터 Set의 크기\n", "1. feature의 갯수 (데이터세트의 차원)\n", "1. 선형적 분리가능 여부\n", "1. feature 서로 관련여부 (다중공선성 여부 확인)\n", "1. Bias, 분산간 Trade Off\n", "1. On Line Learning 여부" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **01 Naive Bayse**\n", "1. 단순한 알고리즘으로, feature가 독립적이면 일반적으로 잘 작동한다\n", "1. 대용량 데이터에도 잘 작동하고, 단순해서 속도가 빠르다\n", "1. 단점으로 Bias가 큰 모델이 생성된다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **02 로지스틱 회귀**\n", "Bias가 낮고, 분산이 높은 알고리즘으로 L1, L2 둘을 합친 정규화 항으로 OverFitting을 해결한다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **03 SVM**\n", "데이터를 선형분리하는 모델로, 로지스틱 회귀보다 성능이 좋다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **04 랜덤 포레스트**\n", "별도의 인코딩 없이도 범주형 feature를 직접 적용가능하다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **05 Nural Network**\n", "복잡한 튜닝에 시간을 많이 소요하므로, 처음부터 접근하기엔 용이하지 않은 부분이 많다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **06 OverFittiing을 줄일 것**\n", "알고리즘 적용결과 Over Fitting을 피하기 위해서\n", "1. 교차검증\n", "1. 정규화\n", "1. 단순화 작업 : Tree Forest 묶음, 다차원 선형회귀, SVM\n", "1. 앙상블 러닝" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **07 OverFittiing / UnderFitting 판단**\n", "1. 이상적인 모델은 **기대수준에 가깝게** Train/ Test 그래프가 **대칭분포할** 경우다\n", "1. **OverFitting 은**, Test 데이터가 많이 처질 때를 의미한다\n", "1. **UnderFitting 은**, Train/ Test 데이터가 같은 영역에 존재할 때이다\n", "1. sklearn 의 **Plot Learning Curve를 참조 [sklearn](http://scikit-learn.org/stable/auto_examples/model_selection/plot_learning_curve.html)\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# **4 모델의 배포, 모니터링 참고 사례**\n", "데이터 전처리, 파이프라인, 학습 모델을 예측한 뒤 마지막 단계로써\n", "1. 만들어진 모델을 **저장**/ 새로운 데이터 모델을 **배포**\n", "1. 주기적 성능 모니터링\n", "1. 정기적 예측모델의 업데이트" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **01 : 모델의 저장, 로딩, 재사용**\n", "1. 관련 단계가 완료 후, 전처리 모델과 학습된 **예측 모델을 저장한다**\n", "1. **위의 모델을 활용하여** 새로운 예측결과를 만드는 데 활용한다" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "from sklearn import datasets\n", "dataset = datasets.load_diabetes()\n", "X, y = dataset.data, dataset.target\n", "\n", "num_new = 30 # the last 30 samples as new data set\n", "X_train = X[:-num_new, :]\n", "y_train = y[:-num_new]\n", "X_new = X[-num_new:, :]\n", "y_new = y[-num_new:]" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "정규화 작업이 완료된 객체를 ./data/scaler.p로 저장\n" ] } ], "source": [ "# 정규화 작업후 객체를 저장\n", "from sklearn.preprocessing import StandardScaler\n", "scaler = StandardScaler()\n", "scaler.fit(X_train)\n", "\n", "import pickle\n", "file_name = \"./data/scaler.p\"\n", "pickle.dump(scaler, open(file_name, \"wb\" ))\n", "X_scaled_train = scaler.transform(X_train)\n", "print('정규화 작업이 완료된 객체를 {}로 저장'.format(file_name))" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "학습이 완료된 객체를 ./data/regressor.p로 저장\n" ] } ], "source": [ "# 작업이 완료된 객체로 모델을 학습\n", "from sklearn.svm import SVR\n", "regressor = SVR(C=20)\n", "regressor.fit(X_scaled_train, y_train)\n", "file_name = \"./data/regressor.p\"\n", "pickle.dump(regressor, open(file_name, \"wb\"))\n", "print(\"학습이 완료된 객체를 {}로 저장\".format(file_name))" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "# 학습이 완료된 객체를 배포한다\n", "my_scaler = pickle.load(open(\"./data/scaler.p\", \"rb\" ))\n", "my_regressor = pickle.load(open(\"./data/regressor.p\", \"rb\"))\n", "X_scaled_new = my_scaler.transform(X_new)\n", "predictions = my_regressor.predict(X_scaled_new)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **02 모델의 성능 모니터링**\n", "객체들이 제대로 돌아가는지를 확인한다" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Health check on the model, R^2: 0.613\n" ] } ], "source": [ "# Monitor\n", "from sklearn.metrics import r2_score\n", "print('Health check on the model, R^2: {0:.3f}'.format(r2_score(y_new, predictions)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **03 정기적인 모델의 업데이트**\n", "성능에 따라서 패턴이 달라진경우에는 모니터링 결과를 통해서 업데이트를 판단하여 재학습 한다" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.9" } }, "nbformat": 4, "nbformat_minor": 4 }