為CharityML尋找捐獻者

在這個項目中,我將使用1994年美國人口普查收集的數據,選用幾個監督學習演算法以準確地建模被調查者的收入。然後,我將根據初步結果從中選擇出最佳的候選演算法,並進一步優化該演算法以最好地建模這些數據。我的目標是建立一個能夠準確地預測被調查者年收入是否超過50000美元的模型。這種類型的任務會出現在那些依賴於捐款而存在的非營利性組織。了解人群的收入情況可以幫助一個非營利性的機構更好地了解他們要多大的捐贈,或是否他們應該接觸這些人。雖然我們很難直接從公開的資源中推斷出一個人的一般收入階層,但是我們可以(也正是我們將要做的)從其他的一些公開的可獲得的資源中獲得一些特徵從而推斷出該值。

這個項目的數據集來自UCI機器學習知識庫。這個數據集是由Ron Kohavi和Barry Becker在發表文章"Scaling Up the Accuracy of Naive-Bayes Classifiers: A Decision-Tree Hybrid"之後捐贈的,可以在Ron Kohavi提供的在線版本中找到這個文章。我們在這裡探索的數據集相比於原有的數據集有一些小小的改變,比如說移除了特徵fnlwgt 以及一些遺失的或者是格式不正確的記錄。

安裝要求

  • Python2
  • NumPy
  • Pandas
  • matplotlib
  • scikit-learn

數據探索

# 顯示第一條記錄ndisplay(data.head(n=1))n

可以選擇計算以下量

  • 總的記錄數量,n_records
  • 年收入大於50,000美元的人數,n_greater_50k.
  • 年收入最多為50,000美元的人數 n_at_most_50k.
  • 年收入大於50,000美元的人所佔的比例, greater_percent.

數據預處理

  • 轉換傾斜的連續特徵

一個數據集有時可能包含至少一個靠近某個數字的特徵,但有時也會有一些相對來說存在極大值或者極小值的不平凡分布的的特徵。演算法對這種分布的數據會十分敏感,並且如果這種數據沒有能夠很好地歸一化處理會使得演算法表現不佳。在人口普查數據集的兩個特徵符合這個描述:capital-gain和capital-loss。

# 將數據切分成特徵和對應的標籤nincome_raw = data[income]nfeatures_raw = data.drop(income, axis = 1)nn# 可視化原來數據的傾斜的連續特徵nvs.distribution(data)n

對於高度傾斜分布的特徵如capital-gain和capital-loss,常見的做法是對數據施加一個對數轉換,將數據轉換成對數,這樣非常大和非常小的值不會對學習演算法產生負面的影響。並且使用對數變換顯著降低了由於異常值所造成的數據範圍異常。但是在應用這個變換時必須小心:因為0的對數是沒有定義的,所以我們必須先將數據處理成一個比0稍微大一點的數以成功完成對數轉換。

# 對於傾斜的數據使用Log轉換nskewed = [capital-gain, capital-loss]nfeatures_raw[skewed] = data[skewed].apply(lambda x: np.log(x + 1))nn# 可視化經過log之後的數據分布nvs.distribution(features_raw, transformed = True)n

  • 規一化數字特徵

除了對於高度傾斜的特徵施加轉換,對數值特徵施加一些形式的縮放通常會是一個好的習慣。在數據上面施加一個縮放並不會改變數據分布的形式(比如上面說的capital-gain or capital-loss);但是,規一化保證了每一個特徵在使用監督學習器的時候能夠被平等的對待。注意一旦使用了縮放,觀察數據的原始形式不再具有它本來的意義了.

# 導入sklearn.preprocessing.StandardScalernfrom sklearn.preprocessing import MinMaxScalernn# 初始化一個 scaler,並將它施加到特徵上nscaler = MinMaxScaler()nnumerical = [age, education-num, capital-gain, capital-loss, hours-per-week]nfeatures_raw[numerical] = scaler.fit_transform(data[numerical])nn# 顯示一個經過縮放的樣例記錄ndisplay(features_raw.head(n = 1))n

  • 獨熱編碼

從上表中,我們可以看到有幾個屬性的每一條記錄都是非數字的。通常情況下,學習演算法期望輸入是數字的,這要求非數字的特徵(稱為類別變數)被轉換。轉換類別變數的一種流行的方法是使用獨熱編碼方案。獨熱編碼為每一個非數字特徵的每一個可能的類別創建一個「虛擬」變數。

此外,對於非數字的特徵,我們需要將非數字的標籤income轉換成數值以保證學習演算法能夠正常工作。因為這個標籤只有兩種可能的類別("<=50K"和">50K"),我們不必要使用獨熱編碼,可以直接將他們編碼分別成兩個類0和1。

# TODO:使用pandas.get_dummies()對features_raw數據進行獨熱編碼nfeatures = pd.get_dummies(features_raw)nn# TODO:將income_raw編碼成數字值nincome = data[income].replace([>50K, <=50K], [1, 0])nn# 列印經過獨熱編碼之後的特徵數量nencoded = list(features.columns)nprint "{} total features after one-hot encoding.".format(len(encoded))n

  • 混洗和切分數據

我們現在將數據(包括特徵和它們的標籤)切分成訓練和測試集。其中80%的數據將用於訓練和20%的數據用於測試。

評價模型性能

  • 評價標準

F-beta score(詳見鏈接)

  • 樸素的預測器

如果我們選擇一個無論什麼情況都預測被調查者年收入大於$50,000的模型,那麼這個模型在這個數據集上的準確率和F-score是多少?

# TODO: 計算準確率naccuracy = greater_percentnn# TODO: 使用上面的公式,並設置beta=0.5計算F-scorenbeta=0.5nTP=n_greater_50knFP=n_at_most_50k nTN=0nFN=0nrecall=TP/(TP+FN)nnprecision=float(TP)/(TP+FP)nnfscore = (1+beta**2)*(precision*recall)/(beta**2*precision+recall)nn# 列印結果nprint "Naive Predictor: [Accuracy score: {:.4f}, F-score: {:.4f}]".format(accuracy, fscore)n

Naive Predictor: [Accuracy score: 0.2478, F-score: 0.2917]

  • 監督學習模型

我選擇的是GaussianNB, Decision Trees, AdaBoost這三種演算法。

    • GaussianNB:
      • 真實應用場景:過濾垃圾郵件(來自Machine Learning in Action | by Peter Harrington 內容4.6)
      • 優勢是在數據較少的情況下仍然有效,可以處理多類別問題。對於文檔分類它的表現最好
      • 缺點是對於輸入數據的準備方式較為敏感。當輸入數據是由多個單片語成且意義明顯不同的短語時,它的表現很差。
      • 對於我們的數據集,它的特徵數很多,而GaussianNB適合處理大量特徵數的數據。

    • Decision Tree:
      • 真實應用場景:預測隱形眼鏡類型(來自Machine Learning in Action | by Peter Harrington 內容3.4)
      • 優勢是計算複雜度不高,輸出結果易於理解,對中間值的確是不敏感,可以處理不相關特徵數據。對於簡單的布爾型數據它的表現最好
      • 缺點是可能出現過擬合問題。當過於依賴數據或參數設置不好時,它的表現很差。
      • 我們的數據中有大量布爾型特徵,適合用Decision Tree,而且它的一些特徵對於我們的目標可能相關程度並不高,而Decision Tree適合處理不相關特徵數的數據。

    • AdaBoost:
      • 真實應用場景:預測患有疝病的馬是否存活(來自Machine Learning in Action | by Peter Harrington 內容7.6)
      • 優勢是泛化錯誤低,易編碼,可以應用在大部分分類器上,無參數調整。對於基於錯誤提升分類器性能它的表現最好
      • 缺點是對離群點敏感。當輸入數據有不少極端值時,它的表現很差。
      • 我們的數據集特徵很多,較為複雜,在後續迭代中,出現錯誤的數據權重可能增大,而針對這種錯誤的調節能力正是AdaBoost的長處。
  • 訓練和預測

# TODO:從sklearn中導入兩個評價指標 - fbeta_score和accuracy_scorenfrom sklearn.metrics import fbeta_score, accuracy_scorenndef train_predict(learner, sample_size, X_train, y_train, X_test, y_test): n n inputs:n - learner: the learning algorithm to be trained and predicted onn - sample_size: the size of samples (number) to be drawn from training setn - X_train: features training setn - y_train: income training setn - X_test: features testing setn - y_test: income testing setn n n results = {}n n # TODO:使用sample_size大小的訓練數據來擬合學習器n # TODO: Fit the learner to the training data using slicing with sample_sizen start = time() # 獲得程序開始時間n n learner = learner.fit(X_train[:sample_size], y_train[:sample_size])n end = time() # 獲得程序結束時間n n # TODO:計算訓練時間n results[train_time] = end-startn n # TODO: 得到在測試集上的預測值n # 然後得到對前300個訓練數據的預測結果n start = time() # 獲得程序開始時間n predictions_test = learner.predict(X_test)n predictions_train = learner.predict(X_train[:300])n end = time() # 獲得程序結束時間n n # TODO:計算預測用時n results[pred_time] = end-startn n # TODO:計算在最前面的300個訓練數據的準確率n results[acc_train] = accuracy_score( y_train[:300], predictions_train)n n # TODO:計算在測試集上的準確率n results[acc_test] = accuracy_score( y_test, predictions_test)n n # TODO:計算在最前面300個訓練數據上的F-scoren results[f_train] = fbeta_score(y_train[:300], predictions_train, 0.5)n n # TODO:計算測試集上的F-scoren results[f_test] = fbeta_score(y_test, predictions_test, 0.5)n n # 成功n print "{} trained on {} samples.".format(learner.__class__.__name__, sample_size)n n # 返回結果n return resultsn

  • 初始模型評估

# TODO:從sklearn中導入三個監督學習模型nfrom sklearn.naive_bayes import GaussianNBnfrom sklearn.tree import DecisionTreeClassifiernfrom sklearn.ensemble import AdaBoostClassifiern# TODO:初始化三個模型nclf_A = GaussianNB()nclf_B = DecisionTreeClassifier(random_state=42)nclf_C = AdaBoostClassifier(random_state=42)nn# TODO:計算1%, 10%, 100%的訓練數據分別對應多少點nsamples_1 = int(0.01*len(X_train))nsamples_10 = int(0.1*len(X_train))nsamples_100 = len(X_train)nn# 收集學習器的結果nresults = {}nfor clf in [clf_A, clf_B, clf_C]:n clf_name = clf.__class__.__name__n results[clf_name] = {}n for i, samples in enumerate([samples_1, samples_10, samples_100]):n results[clf_name][i] = n train_predict(clf, samples, X_train, y_train, X_test, y_test)nn# 對選擇的三個模型得到的評價結果進行可視化nvs.evaluate(results, accuracy, fscore)n

提高效果

  • 選擇最佳的模型

DecisionTree在訓練集上的accuracy score和F-score在三個模型中是最好的,雖然DecisionTree在測試集上的表現沒這麼好,在無參數調整的情況下出現了輕度的過擬合,但調整參數後應該可以消除這個問題,雖然對完整數據它的訓練時間較長,但比AdaBoost快多了,且考慮到它的預測時間短,也就是查詢時間短,我們一旦把模型訓練出來,之後的主要任務就只有查詢了,並不會過多消耗資源和開支,所以我還是決定使用DecisionTree.

  • 模型調優

老樣子——max_depth

我不多說了,還有許多參數可以調的,我是為簡便起見。

  • 最終模型評估

。。。

特徵的重要性

在數據上(比如我們這裡使用的人口普查的數據)使用監督學習演算法的一個重要的任務是決定哪些特徵能夠提供最強的預測能力。通過專註於一些少量的有效特徵和標籤之間的關係,我們能夠更加簡單地理解這些現象,這在很多情況下都是十分有用的。在這個項目的情境下這表示我們希望選擇一小部分特徵,這些特徵能夠在預測被調查者是否年收入大於$50,000這個問題上有很強的預測能力。

  • 提取特徵重要性

# TODO:導入一個有feature_importances_的監督學習模型nn# TODO:在訓練集上訓練一個監督學習模型nmodel = DecisionTreeClassifier()nmodel.fit(X_train,y_train)n# TODO: 提取特徵重要性nimportances = model.feature_importances_nn# 繪圖nvs.feature_plot(importances, X_train, y_train)n

  • 特徵選擇

通過使用更少的特徵來訓練,在評價指標的角度來看我們的期望是訓練和預測的時間會更少。

減小特徵空間後做的新的預測(結果)

Final Model trained on full data

------

Accuracy on testing data: 0.8526

F-score on testing data: 0.7228

Final Model trained on reduced data

------

Accuracy on testing data: 0.8426

F-score on testing data: 0.6801

影響

最終模型和前面的相比F-score下降了太多,準確率下降了一點,就算訓練時間是一個要考慮的因素,我也不會使用部分特徵作為我的訓練集,因為犧牲了太多的F-score,不過如果上面的5個特徵的累計特徵權重達到90%以上,我還是會考慮的。

結尾

我的代碼和數據地址 ciozhang (Jacob Zhang)


推薦閱讀:

【連載之九】拒絕雞湯,我要掙錢!用數據分析找到銷售成功背後的真正秘密
【學習心法】一張圖了解數據分析/挖掘的精髓
如何找到數據分析的工作?是否很困難?
快消與大數據:利用DataHunter制定異常庫存處理策略

TAG:机器学习 | 数据分析 |