機器學習和 scikit-learn 介紹
監督學習介紹
機器學習中,我們通常會接觸到:監督學習、非監督學習、半監督學習,強化學習等不同的應用類型。其中,監督學習(英語:Supervised learning)是最為常見,且應用最為廣泛的分支之一。
監督學習的目標是從已知訓練數據中學習一個預測模型,使得這個模型對于其他輸入數據產生一個預測輸出。其中,監督學習的「監督」是相對與「非監督」的一種表達,二者的區別在于,監督學習的訓練數據經過了人工進行標注,而非監督學習則沒有這個過程。
如同上面的兩個簡單的數據集。左邊的數據集明顯沒有經過標注。而右邊數據集則進行了顏色標注,也就是人為給數據樣本打上了橙色、綠色和藍色的標簽。
監督學習的類型
監督學習中,所面對的問題大致分為兩類:分類和回歸。
其中,分類問題可以簡單概括為:已有了一些數據樣本及明確的樣本分類。現在從這些樣本的特征中總結規律,再用于判斷新輸入樣本到底屬于哪一類別。例如下圖展示了一個分類過程,使用監督學習算法對水果進行類別區分。
面對一個新問題,判斷它是屬于分類還是回歸,就需要根據這個問題具備的特征來判斷。
其中,回歸問題與分類問題的最大區別(特征)在于,輸出變量的類型不同。詳細說來:
分類問題,輸出為有限個離散變量,布爾值或者定類變量。
回歸問題,輸出為連續變量,一般為實數,也就是一個確切值。
非監督學習介紹
在監督學習的過程中,我們需要對訓練數據打上標簽,這是必不可少的一步。而非監督學習面對的數據是沒有標簽的。
比如我們現在有一堆動物的照片。在監督學習中,我們需要提前對每張照片代表的動物進行標記。這一張是狗,那一張是貓,然后再進行訓練。最后,模型對于新輸入的照片,就能分清楚動物的類別。
當進行非監督學習時,照片并未進行標注。我們需要將所有的訓練樣本照片「喂」給算法即可。注意,這個時候和監督學習有一些不同,非監督學習只能識別出訓練樣本里包含了幾種類別的動物,而并不能直接告訴你這只是貓,那一只是狗。但是,這里的類別數量一般都不會太大,你可以手動對類別進行標記,再將數據用于其他用途。
非監督學習識別出樣本包含幾種類別,就是我們通常所說的「聚類」。
實際上非監督學習還包括主成分分析等更多的應用方面。機器學習中,當我們使用到的數據沒有特定標簽時,基本都可以被歸為非監督學習問題。
scikit-learn 介紹
機器學習常用的方法有很多,例如:線性回歸、支持向量機、k 近鄰、決策樹、樸素貝葉斯、邏輯回歸等。scikit-learn 還提供了圍繞機器學習核心算法的一套工具,包括數據預處理,模型評估,超參數優化等。
線性回歸和感知機分類
線性回歸模型
線性模型有最小二乘回歸、感知機、邏輯回歸、嶺回歸,貝葉斯回歸等,由 sklearn.linear_model 模塊導入。對于線性模型而言,即通過擬合線性函數(下圖)去完成樣本分類或回歸預測。
其中,最小二乘回歸、嶺回歸、貝葉斯回歸等是用于解決回歸問題。而感知機、邏輯回歸被用于解決分類問題。
最小二乘法是線性回歸中最經典的方法之一,最小二乘的取名即來自于其選擇了平方損失函數。在 scikit-learn 中,最小二乘法的實現方法如下:
使用 scikit-learn 去解決一個機器學習相關的問題時,我們的代碼都大同小異,主要是由幾個部分組成:
調用一個機器學習方法構建相應的模型 model,并設置模型參數。
使用該機器學習模型提供的 model.fit() 方法訓練模型。
使用該機器學習模型提供的 model.predict() 方法用于預測。
通過最小二乘回歸去擬合二維平面上的一些點。首先,執行第一步,載入方法并構建模型。
import warnings
from sklearn.linear_model import LinearRegression
# 忽略代碼警告,僅供教學方便,自行書寫代碼時警告也很重要,不建議忽略
warnings.filterwarnings('ignore')
model = LinearRegression() # 調用最小二乘法線性回歸(第 1 步)
model
model 模型輸出的參數,即為相應算法類的默認參數。
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
使用模型帶有的 fit() 方法去擬合 3 個點。三個點的特征向量分別為[0,0], [1,1],[2,2],對應的目標值為[1,2,3]。
model.fit([[0, 0], [1, 1], [2, 2]], [1, 2, 3]) # 模型訓練(第 2 步)
訓練時,選擇的[0,0],[1,1],[2,2] 這三個點恰好在一條直線上,再結合目標值想象一下它們的空間位置關系。我們可以使用下面的方法,輸出擬合直線 ww 項和常數項值。
model.coef_, model.intercept_
當我們輸入新的數值,例如[3,3] 時,根據上面的函數,因變量的值為 44。那么,我們使用模型來預測,看一看結果是否為 4。
model.predict([[3, 3]]) # 模型預測(第 3 步)
下面我們導入 scikit-learn 內置的 diabetes 糖尿病數據集來訓練一個復雜一點的最小二乘回歸模型。
第一步:導入數據,并將其劃分為 70% 的訓練集和 30% 的測試集。機器學習中,我們習慣采用這樣的比例來劃分訓練集和測試集。其中訓練集用來訓練模型,而測試集則用來評估模型的質量。測試集的數據不會出現在訓練數據中,這也就類似我們使用了新的數據對訓練好的模型進行預測和評估,以保證模型質量真實可靠。
from sklearn import datasets # 導入內置數據集模塊
from sklearn.model_selection import train_test_split # 導入數據集切分模塊
import numpy as np # 導入數值計算模塊
diabetes = datasets.load_diabetes() # 載入糖尿病數據集
diabetes_feature = diabetes.data[:, np.newaxis, 2] # 該數據集的特征較多,這里只選取其中一個
diabetes_target = diabetes.target # 設定目標值
# 切分數據集為 70% 的訓練集和 30% 的預測集
# random_state 隨機數種子用于保證每次執行結果一致
train_feature, test_feature, train_target, test_target = train_test_split(
diabetes_feature, diabetes_target, test_size=0.3, random_state=56)
第二步:載入最小二乘回歸模型,并訓練數據。
model = LinearRegression() # 構建最小二乘線性回歸模型
model.fit(train_feature, train_target) # 使用訓練集數據訓練模型
第三步:使用測試集進行預測,并將結果繪圖。
import matplotlib.pyplot as plt # 導入 matplotlib 繪圖模塊
%matplotlib inline
# 繪圖
plt.scatter(train_feature, train_target, color='black') # 繪制訓練集散點圖
plt.scatter(test_feature, test_target, color='red') # 繪制測試集散點圖
plt.plot(test_feature, model.predict(test_feature),
color='blue', linewidth=3) # 繪制擬合直線
# 繪制圖例
plt.legend(('Fit line', 'Train Set', 'Test Set'), loc='lower right')
plt.title('LinearRegression Example')
最后,我們可以通過繪制的圖像,更加直觀地看出采用最小二乘回歸模型進行線性擬合的結果。
對于其他常見的線性回歸模型,它們和最小二乘線性回歸模型非常相似,只是采用了不同的損失函數。
例如,嶺回歸采用了帶 L2 懲罰項的平方和損失函數。
而另一種常見的 Lasso 回歸,同樣采用了帶 L1 懲罰項的平方損失函數。
一些常見的廣義線性回歸模型,及它們在 scikit-learn 中對應的方法。
這些方法相對于普通最小二乘回歸模型而言,均增加了一些懲罰項。這樣會提高模型的泛化能力,在實際應用中效果可能會好一些。
線性分類模型
感知機 是一個經典的二分類方法,它是神經網絡和支持向量機的基礎。感知機模型非常簡單,輸入為一些特征向量,輸出則由正類和負類組成。而輸入和輸出之間,則是由符號函數連接。
感知機的損失函數是錯誤分類點到分離超平面之間的距離總和,其學習策略同樣也是損失函數最小化。
實現感知機通過調用 sklearn.linear_model.Perceptron()方法完成。
首先,使用 scikit-learn 提供的 make_classification 方法生成一組可被二分類的二維數組作為數據集。
from sklearn.datasets import make_classification # 導入分類數據生成模塊
# 隨機生成一組可以被二分類的數據
X, y = make_classification(n_features=2, n_redundant=0,
n_informative=1, n_clusters_per_class=1, random_state=1)
X.shape, y.shape # 查看數組形狀
我們可以使用 Matplotlib 將該數據集繪制出來。
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y) # 繪制數據集散點圖
其中,y 即相當于人為給數據添加的標簽。
數據集分為 2 種顏色的樣本點,并呈現出明顯的線性界線。接下來,我們使用感知機對該數據集進行分類訓練。
from sklearn.linear_model import Perceptron
# 將數據集劃分為 70% 訓練集和 30% 測試集
train_feature, test_feature, train_target, test_target = train_test_split(
X, y, test_size=0.3, random_state=56)
# 建立感知機模型,使用默認參數
model = Perceptron()
# 使用訓練集訓練模型
model.fit(train_feature, train_target)
訓練結束后,我們用測試數據進行預測。請注意,由于測試數據與訓練數據完全不同,也是算法之前完全沒有見過的數據。我們后續可以通過模型對測試數據的預測結果,然后與真實的結果進行比較,從而得到模型的分類準確度。
preds = model.predict(test_feature) # 使用測試集預測
準確度表示正確預測的樣本占全部樣本的比例,是用于評估分類模型的常用指標之一,我們得到的 preds
是模型的預測結果,而真實結果為 test_target
。接下來,可以通過 scikit-learn 提供的 accuracy_score 計算出分類準確度。
from sklearn.metrics import accuracy_score
accuracy_score(test_target, preds) # 先傳入真實值,再傳入預測值
返回的結果即是測試集預測分類準確度,如果為 1.0 則表示預測全部正確,分類準確度為 100%。
我們使用 Matplotlib 將訓練數據和測試數據繪制在原圖上,并繪制出感知機分類時的決策邊界。
# 創建一個繪圖矩陣方便顯示決策邊界線
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
fig, ax = plt.subplots()
# 繪制決策邊界
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=plt.cm.Paired)
# 繪制訓練和測試數據
ax.scatter(train_feature[:, 0], train_feature[:, 1])
ax.scatter(test_feature[:, 0], test_feature[:, 1])
支持向量機分類預測
線性支持向量機
感知機的學習過程由誤分類驅動,即當感知機尋找到沒有實例被錯誤分類時,就確定了分割超平面。這樣雖然可以解決一些二分類問題,但是訓練出來的模型往往容易出現過擬合。
如上圖所示,當感知機在進行分類時,為了照顧左下角的兩個紅色標記樣本,分割線會呈現出如圖所示的走向。你應該通過觀察就能發現,這條分割線不是特別合理。
支持向量機也被看成是感知機的延伸。簡單來講,支持向量機就是通過找出一個最大間隔超平面來完成分類。
如圖所示,中間的實線是我們找到的分割超平面。這個超平面并不是隨手一畫,它必須滿足兩個類別中距離直線最近的樣本點,與實線的距離一樣且最大。這里的最大,也就是上面提到的最大間隔超平面。
支持向量機中的「支持向量」指的是上圖中,距離分割超平面最近的樣本點,即兩條虛線上的一個實心點和兩個空心點。
首先,我們需要先導入數據文件
import pandas as pd # 導入 pandas 模塊
import warnings
warnings.filterwarnings('ignore')
# 讀取 csv 數據文件
df = pd.read_csv(
"https://labfile.oss.aliyuncs.com/courses/866/data.csv", header=0)
df.head()
可以直接通過 df.head() 語句查看一下這個數據集頭部,對里面的數據組成初步熟悉一下。
可以看到,有兩個類別。其中 0 即表示上面圖中的藍色樣本點,1 對應著紅色樣本點。
將整個數據集劃分為訓練集和測試集兩部分,其中訓練集占 70%。
from sklearn.model_selection import train_test_split # 導入數據集劃分模塊
# 讀取特征值及目標值
feature = df[["x", "y"]]
target = df["class"]
# 對數據集進行分割
train_feature, test_feature, train_target, test_target = train_test_split(
feature, target, test_size=0.3)
導入線性支持向量機分類器
from sklearn.svm import LinearSVC # 導入線性支持向量機分類器
# 構建線性支持向量機分類模型
model_svc = LinearSVC()
model_svc.fit(train_feature, train_target)
對模型在測試集上的準確度進行評估,之前使用了 accuracy_score ,這里使用模型帶有的 score 方法效果是一樣的
# 支持向量機分類準確度
model_svc.score(test_feature, test_target)
繪制出分類器的決策邊界
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
# 創建一個繪圖矩陣方便顯示決策邊界線
X = feature.values
x_min, x_max = X[:, 0].min() - 0.1, X[:, 0].max() + 0.1
y_min, y_max = X[:, 1].min() - 0.1, X[:, 1].max() + 0.1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
fig, ax = plt.subplots()
# 繪制決策邊界
Z = model_svc.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=plt.cm.Paired)
# 繪制訓練和測試數據
ax.scatter(train_feature.values[:, 0], train_feature.values[:, 1], c=train_target)
ax.scatter(test_feature.values[:, 0], test_feature.values[:, 1], c=test_target)
非線性支持向量機
在實際生活中,我們大部分情況面對的卻是非線性分類問題,因為實際數據往往都不會讓你通過一個水平超平面就能完美分類。
上圖展現的就是一個非線性分類問題,而支持向量機就是解決非線性分類的有力武器。
支持向量機引入了核函數來解決非線性分類的問題。簡單來講,通過核函數,我們可以將特征向量映射到高維空間中,然后再高維空間中找到最大間隔分割超平面完成分類。而映射到高維空間這一步驟也相當于將非線性分類問題轉化為線性分類問題。
第一張圖中,紅藍球無法進行線性分類。
使用核函數將特征映射到高維空間,類似于在桌子上拍一巴掌使小球都飛起來了。
在高維空間完成線性分類后,再將超平面重新投影到原空間。
在將特征映射到高維空間的過程中,我們常常會用到多種核函數,包括:線性核函數、多項式核函數、高斯徑向基核函數等。其中,最常用的就算是高斯徑向基核函數了,也簡稱為 RBF 核。
我們選擇了 digits 手寫數字數據集。digits 數據集無需通過外部下載,可以直接由 scikit-learn 提供的 datasets.load_digits() 方法導入。該數據集的詳細信息如下:
第一步,導入數據并進行初步觀察。
from sklearn import datasets # 導入數據集模塊
# 載入數據集
digits = datasets.load_digits()
# 繪制數據集前 5 個手寫數字的灰度圖
for index, image in enumerate(digits.images[:5]):
plt.subplot(2, 5, index+1)
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
可以用 digits.target[:5] 查看前五張手寫數字對應的實際標簽
digits.target[:5]
通常,我們在處理圖像問題時,都是將圖像的每一個像素轉換為灰度值或按比例縮放的灰度值。有了數值,就可以構建和圖像像素大小相同的矩陣了。在這里,digits 已經預置了每一張圖像對應的矩陣,并包含在 digits.images 方法中。
我們可以通過 digits.images[1] 輸出第 1 張手寫數字對應的 8x8 矩陣。很方便地,scikit-learn 已經將 8x8 矩陣轉換成了方便作為特征變量輸入 64x1 的矩陣,并放在了 digits.data 中。你可以使用 digits.data[1]查看。
digits.data[1]
劃分訓練集和測試集,然后針對測試集進行預測并評估預測精準度
from sklearn.svm import SVC # 導入非線性支持向量機分類器
from sklearn.metrics import accuracy_score # 導入評估模塊
feature = digits.data # 指定特征
target = digits.target # 指定目標值
# 劃分數據集,將其中 70% 劃為訓練集,另 30% 作為測試集
train_feature, test_feature, train_target, test_target = train_test_split(
feature, target, test_size=0.33)
model = SVC() # 建立模型
model.fit(train_feature, train_target) # 模型訓練
results = model.predict(test_feature) # 模型預測
scores = accuracy_score(test_target, results) # 評估預測精準度
scores
最后,模型預測準確度為 44.6%。由于每一次運行時,數據集都會被重新劃分,所以你訓練的準確度甚至會低于 44.6%。
我們在建立模型的時候使用的是默認參數
該模型的確使用了最常用的 RBF 高斯徑向基核函數,這沒有問題。問題出在了 gamma 參數,gamma 是核函數的因數,這里選擇了 auto 自動。自動即表示 gamma 的取值為 1 / 特征數量,這里為 1/64。
嘗試將 gamma 參數的值改的更小一些,比如 0.001。重新建立模型
model = SVC(gamma=0.001) # 重新建立模型
model.fit(train_feature, train_target) # 模型訓練
results = model.predict(test_feature) # 模型預測
scores = accuracy_score(test_target, results) # 評估預測精準度
scores
可以看到,這一次的預測準確度已經達到 98% 了,結果非常理想。所以說,會用 scikit-learn 建立模型只是機器學習過程中最基礎的一步,更加重要的是理解模型的參數,并學會調參使得模型的預測性能更優。
監督學習算法對比評估
K 近鄰
K 近鄰是一種十分常用的監督學習算法。簡單來講,K 近鄰就是假設一個給定的數據集,且數據的類別已經確定。這些數據的特征所構成的特征向量可以映射到對應的特征空間中。現在,假設一個輸入實例,我們可以計算該輸入和其他數據點之間的距離,再通過多數表決的方式,來確定新輸入實例的類別,最后完成分類。
其中,K 近鄰中的「近鄰」代表原有特征空間中與新輸入實例距離最近的那些樣本。而 K 代表距離最近的 K 個樣本。所以,對于 K 近鄰而言。K 值的大小和距離的度量方式(歐式距離或者曼哈頓距離)是其構成的兩個關鍵因素。
如上圖所示,原數據集由紅、綠兩類組成。現在,我們新輸入一個橙色實例,圖中表示了樣本對應在特征空間的位置。現在,我們確定 K = 3,然后可以圈定出距離橙色實例最近的 3 個樣本點。其中,紅色樣本為 1 個,綠色 2 個。根據多數表決的規則,最終確定新輸入的橙色樣本數據被判定為 B 類別。而當我們指定 K = 7 時,紅色樣本 4 個,綠色樣本 3 個,則橙色樣本被判定為 A 類別。
決策樹和隨機森林
決策樹也是一種十分常見的監督學習方法。它是一種特殊的樹形結構,一般由節點和有向邊組成。其中,節點表示特征、屬性或者一個類。而有向邊包含有判斷條件。
如圖所示,決策樹從根節點開始延伸,經過不同的判斷條件后,到達不同的子節點。而上層子節點又可以作為父節點被進一步劃分為下層子節點。一般情況下,我們從根節點輸入數據,經過多次判斷后,這些數據就會被分為不同的類別。這就構成了一顆簡單的分類決策樹。
當我們使用決策樹分類時,對于已有訓練集只建立一顆決策樹。而隨機森林的概念是,對于一個訓練集隨機建立多顆決策樹。而建立這些決策樹時,會采取一種叫 Bootstrap 的取樣方式,即每一次從數據集中又放回的取出一部分數據,再用這部分數據去建立小決策樹。對于隨機森林而言,最終的分類結果由眾多小決策樹輸出類別的眾數確定。下圖展示了一個由 3 顆決策樹構成的隨機森林過程。
由于隨機森林的特點,有效地降低過擬合程度,具有較好的泛化誤差。另外,訓練速度也非常快,模型的表現往往都比較好,是十分受歡迎的一種機器學習方法。
常見監督學習方法
通過同一個示例數據集來對常見的監督學習算法分類性能做一個比較。
為了更方便可視化,這里選用了一個隨機生成的二分類數據集。總共包含 300 條數據,類別為 0 和 1。
import pandas as pd # 加載 pandas 模塊
import warnings
warnings.filterwarnings('ignore')
# 讀取 csv 文件, 并將第一行設為表頭
data = pd.read_csv(
"https://labfile.oss.aliyuncs.com/courses/866/class_data.csv", header=0)
data.head() # 輸出數據預覽
通常情況下,可視化是直觀認識陌生數據的很好方法。這里,我們通過 Matplotlib 來可視化這些數據。畫出數據集的散點圖。
from matplotlib import pyplot as plt # 加載繪圖模塊
%matplotlib inline
plt.scatter(data["X"], data['Y'], c=data['CLASS']) # 繪制散點圖
我們用 c=data['CLASS'] 參數來控制散點的顏色。
接下來,加載本次實驗需要的模塊,以及 scikit-learn 中常見的分類器。
# 集成方法分類器
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import RandomForestClassifier
# 高斯過程分類器
from sklearn.gaussian_process import GaussianProcessClassifier
# 廣義線性分類器
from sklearn.linear_model import PassiveAggressiveClassifier
from sklearn.linear_model import RidgeClassifier
from sklearn.linear_model import SGDClassifier
# K近鄰分類器
from sklearn.neighbors import KNeighborsClassifier
# 樸素貝葉斯分類器
from sklearn.naive_bayes import GaussianNB
# 神經網絡分類器
from sklearn.neural_network import MLPClassifier
# 決策樹分類器
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import ExtraTreeClassifier
# 支持向量機分類器
from sklearn.svm import SVC
from sklearn.svm import LinearSVC
接下來,建立預測模型,采用默認參數即可。由于方法較多,所有這里就不再依次單獨定義模型,而是用列表形式管理。
# 建立模型
models = [
AdaBoostClassifier(),
BaggingClassifier(),
ExtraTreesClassifier(),
GradientBoostingClassifier(),
RandomForestClassifier(),
GaussianProcessClassifier(),
PassiveAggressiveClassifier(),
RidgeClassifier(),
SGDClassifier(),
KNeighborsClassifier(),
GaussianNB(),
MLPClassifier(),
DecisionTreeClassifier(),
ExtraTreeClassifier(),
SVC(),
LinearSVC()
]
# 依次為模型命名
classifier_Names = ['AdaBoost', 'Bagging', 'ExtraTrees',
'GradientBoosting', 'RandomForest', 'GaussianProcess',
'PassiveAggressive', 'Ridge', 'SGD',
'KNeighbors', 'GaussianNB', 'MLP',
'DecisionTree', 'ExtraTree', 'SVC', 'LinearSVC']
然后,劃分數據集。70% 用于訓練,另外 30% 用于測試。
from sklearn.model_selection import train_test_split # 導入數據集切分模塊
feature = data[['X', 'Y']] # 指定特征變量
target = data['CLASS'] # 指定標簽變量
X_train, X_test, y_train, y_test = train_test_split(
feature, target, test_size=.3) # 切分數據集
準備好數據之后,就可以開始模型訓練和測試了。
from sklearn.metrics import accuracy_score # 導入準確度評估模塊
# 遍歷所有模型
for name, model in zip(classifier_Names, models):
model.fit(X_train, y_train) # 訓練模型
pre_labels = model.predict(X_test) # 模型預測
score = accuracy_score(y_test, pre_labels) # 計算預測準確度
print('%s: %.2f' % (name, score)) # 輸出模型準確度
這 16 個分類器最終的準確度均在 80% ~ 90% 之間,差距不是很大。對于這種現象,主要有兩個原因。首先,本次使用的是一個非常規范整潔的線性分類數據集。其次,所有的分類器均采用了默認參數,而 scikit-learn 提供的默認參數一般已經較優。
我們通過可視化的方法將每一個模型在分類時的決策邊界展示出來,這樣能更加直觀的感受到機器學習模型在執行分類預測時發生的變化。
from matplotlib.colors import ListedColormap # 加載色彩模塊
import numpy as np # 導入數值計算模塊
from tqdm.notebook import tqdm
# 繪制數據集
i = 1 # 為繪制子圖設置的初始編號參數
cm = plt.cm.Reds # 為繪制等高線選擇的樣式
cm_color = ListedColormap(['red', 'yellow']) # 為繪制訓練集和測試集選擇的樣式
# 柵格化
x_min, x_max = data['X'].min() - .5, data['X'].max() + .5
y_min, y_max = data['Y'].min() - .5, data['Y'].max() + .5
xx, yy = np.meshgrid(np.arange(x_min, x_max, .1),
np.arange(y_min, y_max, .1))
# 模型迭代
plt.figure(figsize=(20, 10))
for name, model in tqdm(zip(classifier_Names, models)):
ax = plt.subplot(4, 4, i) # 繪制 4x4 子圖
model.fit(X_train, y_train) # 模型訓練
pre_labels = model.predict(X_test) # 模型測試
score = accuracy_score(y_test, pre_labels) # 模型準確度
# 根據類的不同選擇決策邊界計算方法
if hasattr(model, "decision_function"):
Z = model.decision_function(np.c_[xx.ravel(), yy.ravel()])
else:
Z = model.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]
# 繪制決策邊界等高線
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=cm, alpha=.6)
# 繪制訓練集和測試集
ax.scatter(X_train['X'], X_train['Y'], c=y_train, cmap=cm_color)
ax.scatter(X_test['X'], X_test['Y'], c=y_test,
cmap=cm_color, edgecolors='black')
# 圖形樣式設定
ax.set_xlim(xx.min(), xx.max())
ax.set_ylim(yy.min(), yy.max())
ax.set_xticks(())
ax.set_yticks(())
ax.set_title('%s | %.2f' % (name, score))
i += 1
按照上面的步驟執行,就能看到如下圖所示的對比圖了。
上面將決策邊界繪制出來,并用等高線圖顯示。其中,顏色越深表示偏向于黃色散點分類的概率越高,而顏色越淺,則表示偏向紅色散點的概率越高。
為了進一步探索各分類器對于不同特征分布的數據集的適用情況。接下來,我們將原有數據集做一些變換。
sklearn.datasets 這個模塊可以導入一些預設的數據集。其實,不僅如此,這個模塊開提供了一些數據集的生成方法。比如:
sklearn.datasets.make_circles 方法可以生成大圓環包小圓環樣式的數據集。
sklearn.datasets.make_moons 方法可以生成兩個交織間隔圓環樣式的數據集。
from sklearn import datasets
# 生成 200 個包含噪聲的環狀樣本
circles = datasets.make_circles(n_samples=200, noise=.1)
plt.scatter(circles[0][:, 0], circles[0][:, 1], c=circles[1])
# 生成 300 個包含噪聲的月牙狀樣本
moons = datasets.make_moons(n_samples=300, noise=.2, random_state=1)
plt.scatter(moons[0][:, 0], moons[0][:, 1], c=moons[1])
這兩組數據都是無法進行線性分類,所以如果是非線性分類器,其結果應該會好很多。
# 對月牙狀樣本進行分割測試
X_train, X_test, y_train, y_test = train_test_split(
moons[0], moons[1], test_size=0.3)
for name, model in zip(classifier_Names, models):
model.fit(X_train, y_train) # 訓練模型
pre_labels = model.predict(X_test) # 模型預測
score = accuracy_score(y_test, pre_labels) # 計算預測準確度
print('%s: %.2f' % (name, score)) # 輸出模型準確度
K-Means 數據聚類算法應用
K-Means 聚類
監督學習被用于解決分類和回歸問題,而非監督學習主要是用于解決聚類問題。聚類,顧名思義就是將具有相似屬性或特征的數據聚合在一起。聚類算法有很多,最簡單和最常用的就算是 K-Means 算法了。
K-Means,中文譯作 K - 均值算法。從它的名字來講,K 代表最終將樣本數據聚合為 K 個類別。而「均值」代表在聚類的過程中,我們計算聚類中心點的特征向量時,需要采用求相鄰樣本點特征向量均值的方式進行。例如:
K-Means 算法在應用時,相對來上面的例子要復雜一些。現在,假設有如下圖所示的一組二維數據。接下來,我們就一步一步演示 K-Means 的聚類過程。
首先,正如我們前面介紹非監督學習時所說,非監督學習面對的數據都是沒有標簽的。如果我們把下方示例數據的顏色看作是標簽,那么只有一種顏色。
第一步,確定要聚為幾類?也就是 K 值。假設,這里我們想將樣本聚為 3 類。當然,你也完全可以將其聚為 2 類或 4 類,不要受到視覺上的誤導。
這里,我們以 3 類為例。當確定聚為 3 類之后,我們在特征空間上,隨機初始化 3 個類別中心。這里的 3 也就對應著 K 值的大小。
第二步,我們依據這三個隨機初始化的中心,將現有樣本按照與最近中心點之間的距離進行歸類。圖中綠線將全部樣本劃分為三個類別。(中間小三角形是參考線,可以忽略。)
這樣,我們的樣本被劃為為三個區域。現在,我們就要用到上面提到的均值來重新求解 3 個區域對應的新的樣本中心。
如上圖所示,假設我們求解的新樣本中心為三個綠點所示的位置。然后,又重新回到上一步,根據這三個中心重新劃分樣本,再求解中心。
依次迭代,直到樣本中心變化非常小時終止。最終,就可以將全部樣本聚類為三類。
首先,我們導入 Pandas 數據處理模塊,用來解析 CSV 數據文件,并查看文件的組成結構。
import pandas as pd # 導入數據處理模塊
import warnings
warnings.filterwarnings('ignore')
df = pd.read_csv(
"http://labfile.oss.aliyuncs.com/courses/866/cluster_data.csv", header=0) # 導入數據文件
df.head()
可以看到,文件包含兩列,也就是對應在特征空間的 x, y 坐標。接下來,我們用 Matplotlib 將數據繪制成散點圖。
from matplotlib import pyplot as plt # 導入繪圖模塊
%matplotlib inline
X = df['x'] # 定義橫坐標數據
y = df['y'] # 定義縱坐標數據
plt.scatter(X, y) # 繪制散點圖
接下來,開始導入 K-Means 方法進行聚類。scikit-learn 中的聚類算法都包含在 sklearn.cluster 方法下。
from sklearn.cluster import k_means # 導入 K-Means 方法
model = k_means(df, n_clusters=3) # 建立聚類模型
model
model 輸出的結果包含三個數組。其中,第一個數組表示三個聚類中心點坐標。第二個數組表示樣本聚類后類別,第三個數組表示樣本距最近聚類中心的距離總和。
接下來,我們就將聚類的結果繪制出來。
cluster_centers = model[0] # 聚類中心數組
cluster_labels = model[1] # 聚類標簽數組
plt.scatter(X, y, c=cluster_labels) # 繪制樣本并按聚類標簽標注顏色
# 繪制聚類中心點,標記成五角星樣式,以及紅色邊框
for center in cluster_centers:
plt.scatter(center[0], center[1], marker="p", edgecolors="red")
可以看到,聚類的結果已經顯示出來了,聚類中心也做了相應標記。
K 值選擇
我們為什么要聚成 3 類?為什么不可以將數據聚成 10 類?
聚成 10 類當然是可以的,只需要將 n_clusters=10 即可。
model = k_means(df, n_clusters=10) # 建立聚類模型
cluster_centers = model[0] # 聚類中心數組
cluster_labels = model[1] # 聚類標簽數組
plt.scatter(X, y, c=cluster_labels) # 繪制樣本并按聚類標簽標注顏色
# 繪制聚類中心點,標記成五角星樣式,以及紅色邊框
for center in cluster_centers:
plt.scatter(center[0], center[1], marker="p", edgecolors="red")
那我們為什么會選擇 K=3 呢?當我們遇到肉眼看起來不太好確定類別,或者是高維數據時怎么辦呢?
所以,這一系列問題最終可以歸結為一個問題,那就是:當我們在使用 K-Means 聚類時,我們該如何確定 K 值?
啟發式學習算法,被稱之為肘部法則。在上文談到用 print(model) 語句時,它會輸出三個數組。其中,前兩個數組在進行聚類繪圖時已經用到了,但是我們一直沒有使用第三個數組。
model[2] # 打印第三個數組
第三個數組,準確說來只是一個數值。它代表著樣本與最近中心點距離的平方和。當我們的 K 值增加時,也就是類別增加時,這個數值應該是會降低的。直到聚類類別的數量和樣本的總數相同時,也就是說一個樣本就代表一個類別時,這個數值會變成 0。
index = [] # 橫坐標數組
inertia = [] # 縱坐標數組
# K 從 1~ 10 聚類
for i in range(9):
model = k_means(df, n_clusters=i + 1)
index.append(i + 1)
inertia.append(model[2])
# 繪制折線圖
plt.plot(index, inertia, "-o")
我們可以看到,和預想的一樣,樣本距離最近中心點距離的總和會隨著 K 值的增大而降低。其中,畸變程度最大的點被稱之為「肘部」。我們可以看到,這里的「肘部」明顯是 K = 3。這也說明,將樣本聚為 3 類的確是最佳選擇。
當我們完成一項聚類任務之后,我們需要對聚類效果進行評估。其實,上面提到的肘部法則也算是一種評估方法,它讓我們知道當 K 值為多少時,整體聚類的結果更理想。
聚類中還有一種評估方法,叫 輪廓系數。輪廓系數綜合了聚類后的兩項因素:內聚度和分離度。內聚度就指一個樣本在簇內的不相似度,而分離度就指一個樣本在簇間的不相似度。
在 scikit-learn 種,同樣也提供了直接計算輪廓系數的方法。下面,繪制出輪廓系數與聚類 K 值變化的折線圖。
from sklearn.metrics import silhouette_score # 導入輪廓系數計算模塊
index2 = [] # 橫坐標
silhouette = [] # 輪廓系數列表
# K 從 2 ~ 10 聚類
for i in range(8):
model = k_means(df, n_clusters=i + 2)
index2.append(i + 2)
silhouette.append(silhouette_score(df, model[1]))
print(silhouette) # 輸出不同聚類下的輪廓系數
# 繪制折線圖
plt.plot(index2, silhouette, "-o")
輪廓系數越接近于 1,代表聚類的效果越好。我們可以很清楚的看出,K=3 對應的輪廓系數數組最大,也更接近于 1 。
PCA 主成分分析應用
主成分分析
主成分分析 是多元線性統計里面的概念,它的英文是 Principal Components Analysis,簡稱 PCA。主成分分析旨在降低數據的維數,通過保留數據集中的主要成分來簡化數據集。簡化數據集在很多時候是非常必要的,因為復雜往往就意味著計算資源的大量消耗。通過對數據進行降維,我們就能在結果不受影響或受略微影響的同時,減少模型學習時間。
本次實驗所說的降維,不是指降低 NumPy 數組的維度,而是特指減少樣本的特征數量。
主成分分析的數學基原理比較簡單,通過對協方差矩陣進行特征分解,從而得出主成分(特征向量)與對應的權值(特征值)。然后剔除那些較小特征值(較小權值)對應的特征向量,從而達到降低數據維數的目的。
上圖展示了把三維空間的數據降維到二維空間的過程。
sklearn.decomposition.PCA(n_components=None, copy=True, whiten=False, svd_solver='auto')
n_components= 表示需要保留主成分(特征)的數量。
copy= 表示針對原始數據降維還是針對原始數據副本降維。當參數為 False 時,降維后的原始數據會發生改變,這里默認為 True。
whiten= 白化表示將特征之間的相關性降低,并使得每個特征具有相同的方差。
svd_solver= 表示奇異值分解 SVD 的方法。有 4 參數,分別是:auto, full, arpack, randomized。
在使用 PCA 降維時,我們也會使用到 PCA.fit() 方法。.fit() 是 scikit-learn 訓練模型的通用方法,但是該方法本身返回的是模型的參數。所以,通常我們會使用 PCA.fit_transform() 方法直接返回降維后的數據結果。
import numpy as np # 導入數值計算模塊
from sklearn.decomposition import PCA # 導入 PCA 模塊
import warnings
warnings.filterwarnings('ignore')
data = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) # 新建一個 2 維數組
new_data = PCA(n_components=1).fit_transform(data) # 降維成 1 維并返回值
print(data) # 輸出原數據
print(new_data) # 輸出降維后的數據
我們可以看到,原先包含 2 個特征的數組已經降維為 1 組特征。
手寫數字識別聚類
手寫數字數據集一共由 1797 張 8x8 的影像組成。該數據集可以直接通過 scikit-learn 導入,無需到外部下載。
先輸出前 5 張圖像預覽一下
from sklearn import datasets # 導入數據集模塊
import matplotlib.pyplot as plt # 導入繪圖模塊
%matplotlib inline
# 載入數據集
digits_data = datasets.load_digits()
# 繪制數據集前 5 個手寫數字的灰度圖
for index, image in enumerate(digits_data.images[:5]):
plt.subplot(2, 5, index+1)
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
上面的 5 張數字圖像依次為 0,1,2,3,4。之前,我們用支持向量機完成了分類,即預測哪一張圖像代表哪一個數字。現在,我們采用相同的數據集完成聚類分析,即將全部數據集聚為 10 個類別。
首先,我們導入常用的 NumPy 數值計算模塊和 Matplotlib 繪圖模塊。由于原數據集維度達到 16,所以這里要進行 PCA 降維。
from sklearn import decomposition
from sklearn.cluster import KMeans
# 導入數據集
digits_data = datasets.load_digits()
X = digits_data.data
y = digits_data.target
# PCA 將數據降為 2 維
estimator = decomposition.PCA(n_components=2)
reduce_data = estimator.fit_transform(X)
reduce_data.shape
接下來,將降維后的數據聚為 10 類,并將聚類后的結果、聚類中心點、聚類決策邊界繪制出來。
# 建立 K-Means 并輸入數據
model = KMeans(n_clusters=10)
model.fit(reduce_data)
# 計算聚類過程中的決策邊界
x_min, x_max = reduce_data[:, 0].min() - 1, reduce_data[:, 0].max() + 1
y_min, y_max = reduce_data[:, 1].min() - 1, reduce_data[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, .05),
np.arange(y_min, y_max, .05))
result = model.predict(np.c_[xx.ravel(), yy.ravel()])
# 將決策邊界繪制繪制出來
result = result.reshape(xx.shape)
plt.figure(figsize=(10, 5))
plt.contourf(xx, yy, result, cmap=plt.cm.Greys)
plt.scatter(reduce_data[:, 0], reduce_data[:, 1], c=y, s=15)
# 繪制聚類中心點
center = model.cluster_centers_
plt.scatter(center[:, 0], center[:, 1], marker='p',
linewidths=2, color='b', edgecolors='w', zorder=20)
# 圖像參數設置
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
圖中,不同的色塊區域代表一類。這里色塊的顏色沒有意義,只表示類別。散點代表數據,散點的顏色表示數據原始類別。我們可以看出,雖然原始數據已經從 16 維降維 2 維,但某幾個數字的依舊有明顯的成團現象。
除此之外,我們還可以使用分類方法來對比降維前后的數據表現。我們使用隨機森林方法對數據進行分類,并通過交叉驗證得到準確度結果。
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
model = RandomForestClassifier()
cross_val_score(model, X, y, cv=5).mean() # 5 折交叉驗證平均準確度
然后,我們再使用降維后的數據做一次一樣的分類實驗。
estimator = decomposition.PCA(n_components=5) # 從 10 個特征縮減為 5 個特征
X_pca = estimator.fit_transform(X)
model = RandomForestClassifier()
cross_val_score(model, X_pca, y, cv=5).mean() # 5 折交叉驗證平均準確度
特征減少 1 半,實際上準確度減少并不多。也就是說,在客觀條件限制下,我們往往可以以更少維度的數據訓練出準確度可以勉強接受的模型。
聚類學習算法對比評估
更多聚類算法
Mini Batch K-Means
實現方法:sklearn.cluster.MiniBatchKMeans
Mini Batch K-Means 整體上和 K-Means 很相似,它是 K-Means 的一個變種形式。與 K-Means 不同的地方在于,其每次從全部數據集中抽樣小數據集進行迭代。Mini Batch K-Means 算法在不對聚類效果造成較大影響的前提下,大大縮短了計算時間。
Affinity Propagation
實現方法:sklearn.cluster.AffinityPropagation
Affinity Propagation 又被稱為親和傳播聚類。Affinity Propagation 是基于數據點進行消息傳遞的理念設計的。與 K-Means 等聚類算法不同的地方在于,親和傳播聚類不需要提前確定聚類的數量,即 K 值。但是運行效率較低。
Mean Shift
實現方法:sklearn.cluster.MeanShift
MeanShift 又被稱為均值漂移聚類。Mean Shift 聚類的目的是找出最密集的區域, 同樣也是一個迭代過程。在聚類過程中,首先算出初始中心點的偏移均值,將該點移動到此偏移均值,然后以此為新的起始點,繼續移動,直到滿足最終的條件。Mean Shift 也引入了核函數,用于改善聚類效果。除此之外,Mean Shift 在圖像分割,視頻跟蹤等領域也有較好的應用。
Spectral Clustering
實現方法:sklearn.cluster.SpectralClustering
Spectral Clustering 又被稱為譜聚類。譜聚類同樣也是一種比較常見的聚類方法,它是從圖論中演化而來的。譜聚類一開始將特征空間中的點用邊連接起來。其中,兩個點距離越遠,那么邊所對應的權值越低。同樣,距離越近,那么邊對應的權值越高。最后,通過對所有特征點組成的網絡進行切分,讓切分后的子圖互相連接的邊權重之和盡可能的低,而各子圖內部邊組成的權值和經可能高,從而達到聚類的效果。譜聚類的好處是能夠識別任意形狀的樣本空間,并且可以得到全局最優解。
Agglomerative Clustering
實現方法:sklearn.cluster.AgglomerativeClustering
Agglomerative Clustering 又被稱為層次聚類。層次聚類算法是將所有的樣本點自下而上合并組成一棵樹的過程,它不再產生單一聚類,而是產生一個聚類層次。層次聚類通過計算各樣本數據之間的距離來確定它們的相似性關系,一般情況下,距離越小就代表相似度越高。最后,將相似度越高的樣本歸為一類,依次迭代,直到生成一棵樹。由于層次聚類涉及到循環計算,所以時間復雜度比較高,運行速度較慢。
Birch 聚類
實現方法:sklearn.cluster.Birch
Birch 是英文 Balanced Iterative Reducing and Clustering Using Hierarchies 的簡稱,它的中文譯名為「基于層次方法的平衡迭代規約和聚類」,名字實在太長。
Birch 引入了聚類特征樹(CF 樹),先通過其他的聚類方法將其聚類成小的簇,然后再在簇間采用 CF 樹對簇聚類。Birch 的優點是,只需要單次掃描數據集即可完成聚類,運行速度較快,特別適合大數據集。
DBSCAN
實現方法:sklearn.cluster.DBSCAN
DBSCAN 是英文 Density-based spatial clustering of applications with noise 的簡稱,它的中文譯名為「基于空間密度與噪聲應用的聚類方法」,名字同樣很長。
DBSCAN 基于密度概念,要求聚類空間中的一定區域內所包含的樣本數目不小于某一給定閾值。該算法運行速度快,且能夠有效處理特征空間中存在的噪聲點。但是對于密度分布不均勻的樣本集合,DBSCAN 的表現較差。
聚類算法對比
首先,我們從 sklearn.cluster 模塊中,導入各聚類方法。如 K-Means 等方法需要提前確定類別數量,也就是 K 值。判斷的方法很簡單,如果聚類方法中包含 n_clusters= 參數,即代表需要提前指定。這里我們統一確定 K=3。
from sklearn import cluster # 導入聚類模塊
# 對聚類方法依次命名
cluster_names = ['KMeans', 'MiniBatchKMeans', 'AffinityPropagation', 'MeanShift',
'SpectralClustering', 'AgglomerativeClustering', 'Birch', 'DBSCAN']
# 確定聚類方法相應參數
cluster_estimators = [
cluster.KMeans(n_clusters=3),
cluster.MiniBatchKMeans(n_clusters=3),
cluster.AffinityPropagation(),
cluster.MeanShift(),
cluster.SpectralClustering(n_clusters=3),
cluster.AgglomerativeClustering(n_clusters=3),
cluster.Birch(n_clusters=3),
cluster.DBSCAN()
]
接下來,我們對上面提到的 8 種常見的聚類算法做一個對比。這里選擇了一個空間分布由三個團狀圖案組成的示例數據集。
import pandas as pd # 導入數據處理模塊
from matplotlib import pyplot as plt # 導入繪圖模塊
%matplotlib inline
# 讀取數據集 csv 文件
data = pd.read_csv(
"https://labfile.oss.aliyuncs.com/courses/866/data_blobs.csv", header=0)
X = data[['x', 'y']]
plt.scatter(data['x'], data['y'])
然后,我們分別應用 8 種聚類方法對數據進行聚類,并將最終的聚類結果繪制出來。
import numpy as np # 導入數值計算模塊
plot_num = 1 # 為繪制子圖準備
plt.figure(figsize=(20, 10))
# 不同的聚類方法依次運行
for name, algorithm in zip(cluster_names, cluster_estimators):
algorithm.fit(X) # 聚類
# 判斷方法中是否由 labels_ 參數,并執行不同的命令
if hasattr(algorithm, 'labels_'):
algorithm.labels_.astype(np.int)
else:
algorithm.predict(X)
# 繪制子圖
plt.subplot(2, len(cluster_estimators) / 2, plot_num)
plt.scatter(data['x'], data['y'], c=algorithm.labels_)
# 判斷方法中是否由 cluster_centers_ 參數,并執行不同的命令
if hasattr(algorithm, 'cluster_centers_'):
centers = algorithm.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], marker="p", edgecolors="red")
# 繪制圖標題
plt.title(name)
plot_num += 1
在我們指定 n_clusters=3 的方法中,除了 SpectralClustering 出現了三個樣本點漂移,其他幾種方法的結果幾乎是一致的。除此之外,在沒有指定 n_clusters 的聚類方法中,Mean Shift 對于此數據集的適應性較好,而親和傳播聚類方法在默認參數下表現最差。
scikit-learn 提供了一張選擇判斷圖供大家參考。