[譯] 用 Python 程式設計進行糖尿病相關的機器學習

Yuqi發表於2018-04-11

根據 Centers for Disease Control and Prevention,現如今美國大約七分之一的成年人都患有糖尿病。而到了 2050 年,這個比率將會激增到三分之一之多。考慮到這一點,我們今天將要完成的就是:學習如何利用機器學習來幫助我們預測糖尿病。現在開始吧!

資料

糖尿病的資料集來自於 UCI Machine Learning Repository這裡 可以下載。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
diabetes = pd.read_csv('diabetes.csv')
print(diabetes.columns)
複製程式碼
Index([‘Pregnancies’, ‘Glucose’, ‘BloodPressure’, ‘SkinThickness’, ‘Insulin’, ‘BMI’, ‘DiabetesPedigreeFunction’, ‘Age’, ‘Outcome’], dtype=’object’)
複製程式碼
diabetes.head()
複製程式碼

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

糖尿病資料集包含 768 個資料點,每個資料點包含 9 個特徵:

print("dimension of diabetes data: {}".format(diabetes.shape))

複製程式碼
dimension of diabetes data: (768, 9)
複製程式碼

“輸出”就是我們將要預測的特徵,0 表示非糖尿病,1 表示糖尿病。在這 768 個資料點中,500 個被標記為 0,268 個被標記為 1:

print(diabetes.groupby('Outcome').size())
複製程式碼

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

import seaborn as sns
sns.countplot(diabetes['Outcome'],label="Count")
複製程式碼

得出下圖:

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

diabetes.info()
複製程式碼

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

k 近鄰

k 近鄰演算法可以說是最簡單的機器學習演算法。它建立僅包含訓練資料集的模型。為了對一個新的資料點作出預測,演算法將在訓練資料集中找到最近的資料點 - 它的“最近鄰點”。

首先,我們需要考察是否可以確認模型的複雜度和精度之間的聯絡:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(diabetes.loc[:, diabetes.columns != 'Outcome'], diabetes['Outcome'], stratify=diabetes['Outcome'], random_state=66)
from sklearn.neighbors import KNeighborsClassifier
training_accuracy = []
test_accuracy = []
# 從 1 到 10 試驗引數 n_neighbors
neighbors_settings = range(1, 11)
for n_neighbors in neighbors_settings:
    # 建立模型
    knn = KNeighborsClassifier(n_neighbors=n_neighbors)
    knn.fit(X_train, y_train)
    # 記錄訓練集精度
    training_accuracy.append(knn.score(X_train, y_train))
    # 記錄測試集精度
    test_accuracy.append(knn.score(X_test, y_test))
plt.plot(neighbors_settings, training_accuracy, label="training accuracy")
plt.plot(neighbors_settings, test_accuracy, label="test accuracy")
plt.ylabel("Accuracy")
plt.xlabel("n_neighbors")
plt.legend()
plt.savefig('knn_compare_model')
複製程式碼

得出下圖:

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

如上圖所示,y 軸表示的訓練和測試集精度和 x 軸表示的 n 近鄰數呈反比。想象一下,如果我們只選擇一個近鄰,在訓練集的預測是很完美的。但是當加入了更多的近鄰的時候,訓練精度將會下降,這表示僅選用一個近鄰所得到的模型太過複雜。最佳實踐是選擇 9 個左右的近鄰。

參考上圖我們應該選擇 n_neighbors=9。那麼這裡就是:

knn = KNeighborsClassifier(n_neighbors=9)
knn.fit(X_train, y_train)
print('Accuracy of K-NN classifier on training set: {:.2f}'.format(knn.score(X_train, y_train)))
print('Accuracy of K-NN classifier on test set: {:.2f}'.format(knn.score(X_test, y_test)))
複製程式碼
Accuracy of K-NN classifier on training set: 0.79
Accuracy of K-NN classifier on test set: 0.78
複製程式碼

邏輯迴歸

邏輯迴歸是最常用的分類演算法之一。

from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression().fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg.score(X_test, y_test)))
複製程式碼
Training set accuracy: 0.781
Test set accuracy: 0.771
複製程式碼

預設值 C=1 在訓練集的精度是 78%,在測試集的精度是 77%。

logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
print("Training set accuracy: {:.3f}".format(logreg001.score(X_train, y_train)))
print("Test set accuracy: {:.3f}".format(logreg001.score(X_test, y_test)))
複製程式碼
Training set accuracy: 0.700
Test set accuracy: 0.703
複製程式碼

使用 C=0.01 則導致在訓練集和測試集的精度都有所下降。

logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
print("Training set accuracy: {:.3f}".format(logreg100.score(X_train, y_train)))
print("Test set accuracy: {:.3f}".format(logreg100.score(X_test, y_test)))
複製程式碼
Training set accuracy: 0.785
Test set accuracy: 0.766
複製程式碼

使用 C=100 導致在訓練集上的精度略有上升但是在測試集的精度下降,我們可以確定低正則和更復雜的模型也許並不能比預設設定表現更好。

因此我們應該採用預設值 C=1。

我們來將引數視覺化,這些引數是通過學習對三個不同正則化引數 C 的資料集建立的模型所得到的。

正則化比較強(C=0.001)的集合得到的引數越來越靠近零。更仔細的看圖,我們也能發現,對於 C=100,C=1 和 C=0.001,特徵 “DiabetesPedigreeFunction” 係數都是正值。這意味著,不管我們看的是哪個模型,高 “DiabetesPedigreeFunction” 特徵和糖尿病樣本是相關聯的。

diabetes_features = [x for i,x in enumerate(diabetes.columns) if i!=8]
plt.figure(figsize=(8,6))
plt.plot(logreg.coef_.T, 'o', label="C=1")
plt.plot(logreg100.coef_.T, '^', label="C=100")
plt.plot(logreg001.coef_.T, 'v', label="C=0.001")
plt.xticks(range(diabetes.shape[1]), diabetes_features, rotation=90)
plt.hlines(0, 0, diabetes.shape[1])
plt.ylim(-5, 5)
plt.xlabel("Feature")
plt.ylabel("Coefficient magnitude")
plt.legend()
plt.savefig('log_coef')
複製程式碼

得出下圖:

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

決策樹

from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(random_state=0)
tree.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 1.000
Accuracy on test set: 0.714
複製程式碼

在訓練集的精度是 100%,但是測試集的精度就差了很多。這意味著樹過擬合了,所以對新資料的泛化能力很弱。因此,我們需要對樹進行剪枝。

我們設定最大深度 max_depth=3,限制了樹的深度能降低過擬合。這將會導致訓練集上精度的下降,但是在測試集的結果將會改善。

tree = DecisionTreeClassifier(max_depth=3, random_state=0)
tree.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 0.773
Accuracy on test set: 0.740
複製程式碼

決策樹的特徵權重

特徵權重決定了每個特徵對於一棵樹最後決策的重要性。對每個特徵它都是一個 0 到 1 之間的數,0 表示著“完全沒用”而 1 表示“完美預測結果”。特徵權重的總和一定是 1。

print("Feature importances:\n{}".format(tree.feature_importances_))
複製程式碼
Feature importances: [ 0.04554275 0.6830362 0\. 0\. 0\. 0.27142106 0\. 0\. ]
複製程式碼

然後我們將特徵權重視覺化:

def plot_feature_importances_diabetes(model):
    plt.figure(figsize=(8,6))
    n_features = 8
    plt.barh(range(n_features), model.feature_importances_, align='center')
    plt.yticks(np.arange(n_features), diabetes_features)
    plt.xlabel("Feature importance")
    plt.ylabel("Feature")
    plt.ylim(-1, n_features)
plot_feature_importances_diabetes(tree)
plt.savefig('feature_importance')
複製程式碼

得出下圖:

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

特徵 “Glucose”(葡萄糖)是目前位置權重最大的特徵。

隨機森林

讓我們在糖尿病資料集上應用一個包含 100 棵樹的隨機森林:

from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, random_state=0)
rf.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(rf.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(rf.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 1.000
Accuracy on test set: 0.786
複製程式碼

沒做任何調參的隨機森林給出的精度為 78.6%,比邏輯迴歸或者單獨的決策樹都要好。但是,我們還是可以調整 max_features 的設定,看看結果能否更好。

rf1 = RandomForestClassifier(max_depth=3, n_estimators=100, random_state=0)
rf1.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(rf1.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(rf1.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 0.800
Accuracy on test set: 0.755
複製程式碼

並沒有,這意味著隨機森林預設的引數就已經運作的很好了。

隨機森林中的特徵權重

plot_feature_importances_diabetes(rf)
複製程式碼

得出下圖:

![]=(https://datascienceplus.com/wp-content/uploads/2018/03/diabetes_8.png)

和單一決策樹相似,隨機森林的 “Glucose” 特徵權重也比較高,但是還選出了 “BMI” 作為所有特徵中第二高的權重。生成隨機森林時的隨機性要求演算法必須考慮眾多可能的解答,結果就是隨機森林比單一決策樹能夠更完整地捕捉到資料的特徵。

梯度提升

from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=0)
gb.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(gb.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gb.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 0.917
Accuracy on test set: 0.792
複製程式碼

模型有可能會過擬合。為了減弱過擬合,我們可以應用強度更大的剪枝操作來限制最大深度或者降低學習率:

gb1 = GradientBoostingClassifier(random_state=0, max_depth=1)
gb1.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(gb1.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gb1.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 0.804
Accuracy on test set: 0.781
複製程式碼
gb2 = GradientBoostingClassifier(random_state=0, learning_rate=0.01)
gb2.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(gb2.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gb2.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 0.802
Accuracy on test set: 0.776
複製程式碼

降低了模型的複雜度的這兩個方法也都如期降低了訓練集的精度。但是在這個例子中,這幾個方法都沒有提高測試集上的泛化能力。

我們可以將特徵權重視覺化來更深入的研究我們的模型,儘管我們對它並不是很滿意:

plot_feature_importances_diabetes(gb1)
複製程式碼

得出下圖:

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

我們可以看出,梯度提升的樹的特徵權重和隨機森林的特徵權重在某種程度上有些相似,在這個例項中,所有的特徵都被賦予了權重。

支援向量機

from sklearn.svm import SVC
svc = SVC()
svc.fit(X_train, y_train)
print("Accuracy on training set: {:.2f}".format(svc.score(X_train, y_train)))
print("Accuracy on test set: {:.2f}".format(svc.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 1.00
Accuracy on test set: 0.65
複製程式碼

這個模型很明顯過擬合了,訓練集上結果完美但是測試集上僅有 65% 的精度。

SVM(支援向量機)需要所有的特徵做歸一化處理。我們需要重新調整資料的比例,這樣所有的特徵都大致在同一個量綱:

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.fit_transform(X_test)
svc = SVC()
svc.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.2f}".format(svc.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.2f}".format(svc.score(X_test_scaled, y_test)))
複製程式碼
Accuracy on training set: 0.77
Accuracy on test set: 0.77
複製程式碼

資料歸一化導致了巨大的不同!現在其實欠擬合了,訓練集和測試集的表現相似但是距離 100% 的精度還有點遠。此時,我們可以試著提高 C 或者 gamma 來生成一個更復雜的模型。

svc = SVC(C=1000)
svc.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
    svc.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(svc.score(X_test_scaled, y_test)))
複製程式碼
Accuracy on training set: 0.790
Accuracy on test set: 0.797
複製程式碼

這裡,提高 C 優化了模型,使得測試集上的精度變成了 79.7%。

深度學習

from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(random_state=42)
mlp.fit(X_train, y_train)
print("Accuracy on training set: {:.2f}".format(mlp.score(X_train, y_train)))
print("Accuracy on test set: {:.2f}".format(mlp.score(X_test, y_test)))
複製程式碼
Accuracy on training set: 0.71
Accuracy on test set: 0.67
複製程式碼

多層感知器(Multilayer perceptrons)的精度遠不如其他模型的好,這可能是因為資料的量綱。深度學習演算法同樣希望所有輸入特徵歸一化,並且最好均值為 0,方差為 1。我們必須重新調整資料,讓它滿足這些要求。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.fit_transform(X_test)
mlp = MLPClassifier(random_state=0)
mlp.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
    mlp.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))
複製程式碼
Accuracy on training set: 0.823
Accuracy on test set: 0.802
複製程式碼

讓我們提高迭代次數:

mlp = MLPClassifier(max_iter=1000, random_state=0)
mlp.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
    mlp.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))
複製程式碼
Accuracy on training set: 0.877
Accuracy on test set: 0.755
複製程式碼

提高迭代次數僅僅優化了模型在訓練集的表現,測試集的表現並沒有改變。

讓我們提高 alpha 引數,並增強權重正則性:

mlp = MLPClassifier(max_iter=1000, alpha=1, random_state=0)
mlp.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
    mlp.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))
複製程式碼
Accuracy on training set: 0.795
Accuracy on test set: 0.792
複製程式碼

結果很好,但是我們沒能夠進一步提高測試集精度。

因此,目前為止最好的模型就是歸一化後預設的深度學習模型。

最後,我們繪製學習糖尿病資料集的神經網路的第一層權重的熱圖。

plt.figure(figsize=(20, 5))
plt.imshow(mlp.coefs_[0], interpolation='none', cmap='viridis')
plt.yticks(range(8), diabetes_features)
plt.xlabel("Columns in weight matrix")
plt.ylabel("Input feature")
plt.colorbar()
複製程式碼

得出下圖:

[譯] 用 Python 程式設計進行糖尿病相關的機器學習

從熱圖上很難很快就看出,相比於其他特徵哪些特徵的權重比較低。

總結

為了分類和迴歸,我們試驗了各種各樣的機器學習模型,(知道了)它們的優點和缺點都是什麼,以及如何控制每個模型的複雜度。我們發現對於很多演算法,設定合適的引數對於模型的上佳表現至關重要。

我們應該知道了如何應用、調參,並分析上文中我們試驗過的模型。現在輪到你了!試著在 scikit-learn 的內建資料集或者其他你選擇的任意資料集上應用這些演算法中的任一一個。快樂的進行機器學習吧!

這篇部落格上的原始碼可以在這裡找到。關於上文內容,我很樂意收到你們的反饋和問題。

參考連結:Introduction to Machine Learning with Python


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章