1. 程式人生 > >機器學習之監督學習supervised learning

機器學習之監督學習supervised learning

分類與迴歸

監督學習的問題主要有兩種,分別是分類classification和迴歸regression。

分類: 分類問題的目的是預測類別標籤class label,這些標籤來自預定義的可選列表。
迴歸: 迴歸任務的目的是預測一個連續值,也叫作浮點數floating-point number,即預測值不是一個類別而是一個數字值。打個比方,假如要根據一個人的年齡學歷等feature來預測這個人的收入,那麼預測值為一個金額,可以在給定範圍內任意取值。

區分分類與迴歸: 最好的辦法就是看輸出是否具有某種連續性,如果在可能的結果之間具有連續性,那麼它就是一個迴歸問題。

泛化 generalize:

如果一個模型能對沒有見過的資料做出準確的預測,那麼就表明這個模型能從訓練集generalize到測試集。

過擬合 overfitting 欠擬合 underfitting: 如果我們總想找到最簡單的模型,構建與一個對於現有資訊量過於複雜的模型,即在擬合模型的時候過分關注訓練集的細節,得到了一個與訓練集上表現很好但是不能泛化到新資料上的模型,那麼就是overfitting過擬合。反之,如果模型過於簡單,無法抓住資料的全部內容以及資料中的變化,甚至在訓練集上表現就很差,那麼就是underfitting欠擬合。
所以,在二者之間存在一個最佳位置,找到這個位置就是我們最想要的模型。

監督學習演算法 supervised learning algorithm:
開始介紹最常用的機器學習演算法,以及每個演算法的複雜度,適用資料,如何構建模型。其中許多演算法啥都有分類和迴歸兩種形式。

首先模型都必須基於資料集進行構建,所以我們先了解一下資料集
模擬資料集:

  • 二分類資料集forge資料集:X, y = mglearn.dataset.make_forge()
  • 迴歸資料集wave資料集:X, y = mglearn.datasets.make_wave(n_samples=40)

現實世界資料集:

  • 分類資料集:乳腺癌資料集,每個腫瘤都被標記為良性或者惡性。30個特徵
  • 迴歸資料集:波士頓放假資料集,根據犯罪率,地理位置等資訊預測房價中位數。13個特徵

有時候我們還需要拓展訓練集,比如輸入feature不僅包括這13個features,還可以包括這些feature之間的乘積也叫作互動項

,像這樣匯出特徵的方法叫做特徵工程feature engineering


K近鄰:

k-NN演算法算是最簡單的機器學習演算法,構建模型只需要儲存訓練資料集,想要對新的資料點做出預測,演算法會在訓練集中找到最近的資料點。

使用k近鄰分類classification:

mglearn.plots.plot_knn_classification(n_neighbors=k)
最簡單的版本就是考慮一個最近鄰k=1,我們還可以考慮k個最近鄰,在這種情況下,我們使用投票法來指定label,我們看哪個類別的最近鄰更多,就將這個類別作為預測結果。

下面我們應用這個k近鄰演算法:

from sklearn.model_selection import train_test_split
X, y = mglearn.datasets.make_forge()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)  # 將資料集分為訓練資料集和測試資料集
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)  # 將這個模型例項化
clf.fit(X_train, y_train)  # 使用訓練資料集對這個分類器進行擬合
print("Test set predictions:", clf.predict(X_test))  # 對測試資料集進行預測
print("Test set accuracy: {:.2f}".format(clf.score(X_test, y_test)))  # 評估模型泛化能力

下面我們分析這個KNeighborsClassifier:
對於二維資料集,我們可以在平面上畫出所有可能的測試點的預測結果,然後根據每個點的所屬類別進行著色,這樣可以檢視兩個類別資料的分界線,也就是決策邊界decision boundary

fig, axes = plt.subplots(1, 3, figsize=(10, 3))
for n_neighbors, ax in zip([1, 3, 9], axes):
    # the fit method returns the object self, so we can instantiate
    # and fit in one line
    clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)
    mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title("{} neighbor(s)".format(n_neighbors))
    ax.set_xlabel("feature 0")
    ax.set_ylabel("feature 1")
axes[0].legend(loc=3)

然後我們發現k=1的時候,決策邊界緊跟著訓練資料,k越大,邊界越平滑,更平滑的邊界對應著更簡單的模型。
接下來,我們來畫出模型複雜度與泛化能力之間的關係:

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=66)
training_accuracy = []
test_accuracy = []
# try n_neighbors from 1 to 10
neighbors_settings = range(1, 11)
for n_neighbors in neighbors_settings:
    # build the model
    clf = KNeighborsClassifier(n_neighbors=n_neighbors)
    clf.fit(X_train, y_train)
    # record training set accuracy
    training_accuracy.append(clf.score(X_train, y_train))
    # record generalization accuracy
    test_accuracy.append(clf.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()

在這裡插入圖片描述
通過這張圖,我們可以看到過擬合與欠擬合的一些特性,比如當k=1的時候,模型對於訓練資料集上的預測結果非常的完美,但是測試資料集上的精確度比更多近鄰的時候精確度低。這樣看來,最佳效能的應該在中間的某處,這樣對於訓練和測試資料集的精度都比較好。

k近鄰迴歸regression:

在迴歸問題上,使用多個近鄰的預測結果為這些鄰居的平均值。使用方法跟分類問題基本一致:

from sklearn.neighbors import KNeighborsRegressor
X, y = mglearn.datasets.make_wave(n_samples=40)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
reg = KNeighborsRegressor(n_neighbors=3)
reg.fit(X_train, y_train)
print("Test set predictions:\n", reg.predict(X_test))
print("Test set R^2: {:.2f}".format(reg.score(X_test, y_test)))

對於迴歸問題的score方法,返回的是R的平方,這個分數也叫做決定係數,位於0~1之間,等於1的時候為完美預測,等於0的時候代表總是預測訓練資料集的平均值。

演算法總結:
KNeighbors分類器有兩個引數,即鄰居的個數以及資料點之間距離的度量方法,往往使用較小的鄰居數就能得到比較好的結果,距離的度量方法預設使用歐式距離,這個方法大多數情況效果良好。在訓練資料集很大的情況下,預測速度會比較慢,這個演算法對於features很多的資料集處理很不好,對於大多數feature取值為0的資料集(稀疏資料集)來說,演算法效果非常不好。所以這個演算法在實踐中往往不會用到。

線性模型linear:

線性模型在實踐中被廣泛使用,線性模型是利用輸入features的線性函式來進行預測的。

用於迴歸regression的線性模型:

y = w[0]*x[0]+w[1]*x[1]+....+w[p]*x[p]+b
x[0]到x[p]為單個數據點的特徵,w和b為學習模型的引數,所以對於單一特徵來說預測結果是一條直線,對於兩個特徵是一個平面,更多特徵即更高維度是一個超平面。
如果資料集有多個特徵,那麼線性模型很強大,尤其是當特徵數量大於訓練資料集數量時,任何目標y都可以在訓練集上用線性函式完美的擬合。下面我們介紹許多不同的線性迴歸模型,這些模型之間的差異就是它們如何從訓練資料集中學習引數w和b,以及如何控制模型的複雜度。

  1. 線性迴歸,也叫作普通最小二乘法,ordinary least squares OLS:
    線性迴歸尋找引數b和w,使對訓練集的預測值與真實的迴歸目標值之間的均方誤差mean squared error最小,這個誤差是預測值與真實值之差的平方和除以sample數量。但是線性迴歸沒有引數,所以無法控制模型的複雜度。
from sklearn.linear_model import LinearRegression
X, y = mglearn.datasets.make_wave(n_samples=60)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
lr = LinearRegression().fit(X_train, y_train)
print("lr.coef_:", lr.coef_)  # 【0.394】這個應該是一個numpy陣列,每個元素對應一個輸入特徵,本例只有一個輸入特徵
print("lr.intercept_:", lr.intercept_)  # -0.031
print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))  # 0.67
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))  # 0.66  與訓練集分數太接近說明可能存在欠擬合

對於一維資料集來說,過擬合風險很小,因為模型非常簡單,但是對於高維資料集來說,線性模型變得更加強大,過擬合的風險也會變大。然後我們把這個模型用於波士頓房價訓練集,結果發現訓練集分數為0.95,但是測試集分數為0.61,這是很明顯的過擬合標誌,所以我們需要一個可以控制模型複雜度的模型,那麼標準的線性迴歸的常用替代方法之一就是嶺迴歸ridge regression。

  1. 嶺迴歸ridge regression:L2正則化
    它的預測公式與普通最小二乘法的公式相同,但是對係數w的選擇不僅要在訓練集上表現良好,還需要擬合附加的約束,還希望所有的w都應該接近於0,即斜率很小,即每個feature對輸出的影響儘可能小的情況下同時給出很好的預測結果。
    這種約束就是正則化regularization,為避免過擬合,嶺迴歸用到的這種被稱為L2正則化。
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=10).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge.score(X_train, y_train)))  # 0.89
print("Test set score: {:.2f}".format(ridge.score(X_test, y_test)))  # 0,75

可以發現ridge在訓練集上的分數比linear regression要低,但是在測試資料集上的分數更高。因為ridge是一種約束能力更強的模型,所以更不容易存在過擬合。複雜度更小的模型意味著在訓練集上效能更差,但是泛化能力更好,但是我們恰恰只對泛化能力感興趣,所以選擇ridge。

ridge模型對於模型的簡單性即係數趨向於0以及訓練集效能之間做出權衡,可以通過設定alpha引數來設定。上面例子預設使用alpha=1,增大alpha會使得係數更加趨向於0==》降低訓練集效能,但是可能會提高泛化能力。對於非常小的alpha可以讓係數收到的限制更小,會得到一個與linear regression類似的模型。

無論是嶺迴歸還是線性迴歸,所有資料集大小對應的訓練分數都要高於測試分數,隨著模型的可用資料越來越多,兩個模型的效能都在提升,最終線性迴歸追上了嶺迴歸,所以,只要有足夠多的訓練資料集,正則化就不那麼重要了

  1. lasso迴歸lasso regression:L1正則化
    與ridge regression相似,使用lasso也是約束係數使之接近於0,但是使用的方法不一樣,是L1正則化,使用L1正則化的結果是某些係數剛好等於0,這說明某些features被模型完全的忽略。不過這樣模型更容易解釋,也可以呈現模型最重要的特徵。
from sklearn.linear_model import Lasso
lasso = Lasso().fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso.score(X_train, y_train)))  # 0.29
print("Test set score: {:.2f}".format(lasso.score(X_test, y_test)))  # 0.21
print("Number of features used:", np.sum(lasso.coef_ != 0))  # 4

上述例子我們可以發現,模型在訓練集和測試集上面的表現都很差,這表示存在欠擬合,可以看到模型中的特徵只用到了4個,所以我們讓模型更加擬合,適當減少alpha引數的值。同時,我們還需要增加max_iter的值,即執行迭代的最大次數:
lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
這樣結果比較好,最後得出使用到模型特徵105箇中的33個,模型也更容易理解,但是alpha也不能設定得太小,因為可能會消除正則化的效果,使模型過擬合。

我們可以對不同模型的係數進項作圖:

plt.plot(lasso.coef_, 's', label="Lasso alpha=1")
plt.plot(lasso001.coef_, '^', label="Lasso alpha=0.01")
plt.plot(lasso00001.coef_, 'v', label="Lasso alpha=0.0001")

plt.plot(ridge01.coef_, 'o', label="Ridge alpha=0.1")
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")

在這裡插入圖片描述

比較ridge regression和lasso regression:
在實踐中,兩個模型一般首選ridge regression。但是如果發現數據集特徵很多但是隻有其中幾個是很重要的,那麼選擇lasso regression可能會更好。在sklearn中,還提供了ElasticNet類,結合了ridge和lasso的懲罰項,在實際操作中這種結合的效果最好,只是需要調節兩個引數:L1正則化和L2正則化。

用於分類classification的線性模型:

y = w[0]*x[0]+w[1]*x[1]+....+w[p]*x[p]+b > 0
如果函式值小於0為類別a,如果函式值大於0為類別b,對於所有用於分類的線性模型,這個規則都是通用的。

對於regression的線性模型,輸出y是特徵的線性模型,是直線,平面或者超平面。但是對於用於分類的線性模型,決策邊界是輸入的線性函式,也就是說,線性分類器是利用直線,平面或者超平面來分開兩個類別的分類器。

最常見的兩種線性分類演算法是邏輯迴歸Logistic regression(LogisticRegression中實現)線性支援向量機linear support vector machine(LinearSVC中實現),其中要注意邏輯迴歸名字中有迴歸,但是這是一種分類演算法。這兩個模型都預設使用了L2正則化。但是對於這兩個模型,決定正則化強度的權衡引數叫做C,C值越大,對應的正則化越弱,那麼LogisticRegression和LinearSVC將盡可能的將訓練集擬合到最好,反之,如果C值較小,那麼模型更強調使係數向量更接近於0。除此之外,C值還有一個作用,較小的值可以讓演算法儘量適應大多數資料點,較大的值更強調每個資料點都分類正確的重要性:
在這裡插入圖片描述

  1. 邏輯迴歸logistic regression:預設L2正則化
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
logreg = LogisticRegression(C=1).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg.score(X_train, y_train)))  # 0.953
print("Test set score: {:.3f}".format(logreg.score(X_test, y_test)))  # 0.958

這裡C=1其實是預設值,我們可以發現C=1給出了相當不錯的精度,但是由於訓練集和測試集的效能太接近,我們有理由懷疑這個模型可能是欠擬合的,所以我們嘗試增加引數C來將訓練集擬合得更好。使用C=100時,我們得到0.972/0.965,可以發現我們的猜想是正確的,因為這個更復雜的模型效能更好,得到了更高的訓練集和測試集精度。使用C=0.01時,我們得到一個正則化更強的模型,得到精度0.934/0.930,可以看出我們將已經就比較欠擬合的模型變得更加欠擬合了,所以精度雙雙降低。

邏輯迴歸預設使用L2正則化,但是如果我們想要一個可解釋性更強的模型,使用L1正則化可能會更好,因為這樣模型只使用少數幾個特徵:
lr_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
我們可以使用penalty引數來改變正則方式,也會影響這個模型是使用全部features還是其中的部分features。

  1. LinearSVC:
    很多線性分類器只使用於二分類,不能輕易推廣到多類別問題,除了邏輯迴歸。
    **多分類原理:**將二分類推廣到多分類,最常見的方法是one vs rest,即對每個類別都學習一個二分類模型,將這個類別與所有其他的類別儘量分開,這樣就生成了與類別數量一樣多的二分類模型。然後我們在測試點上執行所有的二分類模型,對應類別上分數最高的二分類模型即為預測結果。

我們使用one vs rest方法應用到三分類資料集上:

from sklearn.datasets import make_blobs
X, y = make_blobs(random_state=42)
linear_svm = LinearSVC().fit(X, y)
print("Coefficient shape: ", linear_svm.coef_.shape)  #(3,2)
print("Intercept shape: ", linear_svm.intercept_.shape)  # (3,)

coef_每行是三個類別之一的係數向量,每列是每個特徵對應的係數向量,然而intercept_是一個一維陣列,即每個類別的截距。
然後我們將這3個二類分類器給出的直線視覺化:

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,
                                  mglearn.cm3.colors):
    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)
plt.ylim(-10, 15)
plt.xlim(-10, 8)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1','Line class 2'], loc=(1.01, 0.3))

在這裡插入圖片描述

在這裡插入圖片描述

總結優缺點:
線性模型的主要引數是正則化引數,在迴歸模型中為alpha,在LinearSVC和logistic regression中叫做C。alpha較大或者C較小,說明模型比較簡單,通常在對數尺度上對這兩個引數進行搜尋。

還需要確定使用L1還是L2正則化,如果只有幾個特徵比較重要或者需要一個可解釋的模型,那麼可以使用L1正則化,否則預設使用L2正則化。

線性模型的訓練速度很快,預測速度也很快,如果特徵數量大於樣本數量,線性模型的表現都比較好,所以常用於非常大的資料集。

樸素貝葉斯分類器Naive Bayes

Naive Bayes(後面簡稱NB)是與線性模型非常相似的一種分類器,訓練速度往往更快,但是泛化能力會比線性分類器稍差。NB高效的原因在於他是通過單獨檢視每個特徵來學習引數的。

SKlearn中有三種樸素貝葉斯分類器:

  • GaussianNB: 用於任意連續資料,儲存每個類別中每個特徵的平均值和標準差。
  • BernoulliNB: 假定輸入資料為二分類資料,主要用於文字資料分類,計算每個類別中特徵不為0的元素的個數
  • MultinomialNB: 假定輸入資料為計數資料(比如一個單詞在一個句子裡面出現的次數),主要用於文字資料分類。計算每個類別中每個特徵的平均值。

我們在這裡解釋一個BermoulliNB模型:這個分類器計算每個類別中特徵不為0的元素的個數

X = np.array([[0, 1, 0, 1],
              [1, 0, 1, 1],
              [0, 0, 0, 1],
              [1, 0, 1, 0]])
y = np.array([0, 1, 0, 1])

可以看出我們有4個數據點,每個點有4個二分類特徵。從y中我們可以看出一共有兩個類別為0和1,對於類別0,第一個和第三個資料點為類別0。即[0,1,0,1]和[0,0,0,1],第一個特徵有兩個為0,沒有不為0的,第二個特徵…以此類推,計算每個類別中非0元素個數:

counts = {}
for label in np.unique(y):
    counts[label] = X[y == label].sum(axis=0)
print("Feature counts:\n", counts)
Feature counts:
 {0: array([0, 1, 0, 2]), 1: array([2, 0, 2, 1])}

優缺點總結:
MultinominalNBBernoulliNB只有一個引數alpha,用於控制模型的複雜度。alpha的工作原理:演算法向資料中新增alpha這麼多個虛擬資料點,這些點對所有的features都取正值。所以這樣可以將統計資料平滑化,alpha越大,平滑性越強,模型複雜度越低。但是alpha值對模型的效能並不重要,通常只是對精度略有提高。這兩個模型廣泛用於稀疏計數資料,比如文字。
GaussianNB主要用於高維資料。

決策樹 decision tree:

決策樹是廣泛用於分類和迴歸任務的模型,本質上是從一層層if/else問題中進行學習得出結論的:
mglearn.plots.plot_animal_tree()
在這裡插入圖片描述
如圖所示,樹的每一個節點代表一個問題或者一個包含答案的終結點(葉節點),樹的連線將問題的答案與即將要問到的下一個問題連線起來。