10scikit-learn 機(jī)器學(xué)習(xí)基礎(chǔ)入門

機(jī)器學(xué)習(xí)和 scikit-learn 介紹

監(jiān)督學(xué)習(xí)介紹

機(jī)器學(xué)習(xí)中,我們通常會(huì)接觸到:監(jiān)督學(xué)習(xí)、非監(jiān)督學(xué)習(xí)东囚、半監(jiān)督學(xué)習(xí),強(qiáng)化學(xué)習(xí)等不同的應(yīng)用類型战授。其中页藻,監(jiān)督學(xué)習(xí)(英語:Supervised learning)是最為常見桨嫁,且應(yīng)用最為廣泛的分支之一。
監(jiān)督學(xué)習(xí)的目標(biāo)是從已知訓(xùn)練數(shù)據(jù)中學(xué)習(xí)一個(gè)預(yù)測模型份帐,使得這個(gè)模型對(duì)于其他輸入數(shù)據(jù)產(chǎn)生一個(gè)預(yù)測輸出璃吧。其中,監(jiān)督學(xué)習(xí)的「監(jiān)督」是相對(duì)與「非監(jiān)督」的一種表達(dá)废境,二者的區(qū)別在于畜挨,監(jiān)督學(xué)習(xí)的訓(xùn)練數(shù)據(jù)經(jīng)過了人工進(jìn)行標(biāo)注,而非監(jiān)督學(xué)習(xí)則沒有這個(gè)過程噩凹。

image.png

如同上面的兩個(gè)簡單的數(shù)據(jù)集巴元。左邊的數(shù)據(jù)集明顯沒有經(jīng)過標(biāo)注。而右邊數(shù)據(jù)集則進(jìn)行了顏色標(biāo)注栓始,也就是人為給數(shù)據(jù)樣本打上了橙色务冕、綠色和藍(lán)色的標(biāo)簽。
監(jiān)督學(xué)習(xí)的類型
監(jiān)督學(xué)習(xí)中幻赚,所面對(duì)的問題大致分為兩類:分類和回歸禀忆。
其中,分類問題可以簡單概括為:已有了一些數(shù)據(jù)樣本及明確的樣本分類÷淠眨現(xiàn)在從這些樣本的特征中總結(jié)規(guī)律箩退,再用于判斷新輸入樣本到底屬于哪一類別。例如下圖展示了一個(gè)分類過程佳谦,使用監(jiān)督學(xué)習(xí)算法對(duì)水果進(jìn)行類別區(qū)分戴涝。
image.png

面對(duì)一個(gè)新問題,判斷它是屬于分類還是回歸钻蔑,就需要根據(jù)這個(gè)問題具備的特征來判斷啥刻。
其中,回歸問題與分類問題的最大區(qū)別(特征)在于咪笑,輸出變量的類型不同可帽。詳細(xì)說來:
分類問題,輸出為有限個(gè)離散變量窗怒,布爾值或者定類變量映跟。
回歸問題,輸出為連續(xù)變量扬虚,一般為實(shí)數(shù)努隙,也就是一個(gè)確切值。

非監(jiān)督學(xué)習(xí)介紹

在監(jiān)督學(xué)習(xí)的過程中辜昵,我們需要對(duì)訓(xùn)練數(shù)據(jù)打上標(biāo)簽荸镊,這是必不可少的一步。而非監(jiān)督學(xué)習(xí)面對(duì)的數(shù)據(jù)是沒有標(biāo)簽的。
比如我們現(xiàn)在有一堆動(dòng)物的照片贷洲。在監(jiān)督學(xué)習(xí)中收厨,我們需要提前對(duì)每張照片代表的動(dòng)物進(jìn)行標(biāo)記。這一張是狗优构,那一張是貓,然后再進(jìn)行訓(xùn)練雁竞。最后钦椭,模型對(duì)于新輸入的照片,就能分清楚動(dòng)物的類別碑诉。
當(dāng)進(jìn)行非監(jiān)督學(xué)習(xí)時(shí)彪腔,照片并未進(jìn)行標(biāo)注。我們需要將所有的訓(xùn)練樣本照片「喂」給算法即可进栽。注意德挣,這個(gè)時(shí)候和監(jiān)督學(xué)習(xí)有一些不同,非監(jiān)督學(xué)習(xí)只能識(shí)別出訓(xùn)練樣本里包含了幾種類別的動(dòng)物快毛,而并不能直接告訴你這只是貓格嗅,那一只是狗。但是唠帝,這里的類別數(shù)量一般都不會(huì)太大屯掖,你可以手動(dòng)對(duì)類別進(jìn)行標(biāo)記,再將數(shù)據(jù)用于其他用途襟衰。
非監(jiān)督學(xué)習(xí)識(shí)別出樣本包含幾種類別贴铜,就是我們通常所說的「聚類」。
實(shí)際上非監(jiān)督學(xué)習(xí)還包括主成分分析等更多的應(yīng)用方面瀑晒。機(jī)器學(xué)習(xí)中绍坝,當(dāng)我們使用到的數(shù)據(jù)沒有特定標(biāo)簽時(shí),基本都可以被歸為非監(jiān)督學(xué)習(xí)問題苔悦。

scikit-learn 介紹

機(jī)器學(xué)習(xí)常用的方法有很多轩褐,例如:線性回歸、支持向量機(jī)间坐、k 近鄰灾挨、決策樹、樸素貝葉斯竹宋、邏輯回歸等劳澄。scikit-learn 還提供了圍繞機(jī)器學(xué)習(xí)核心算法的一套工具,包括數(shù)據(jù)預(yù)處理蜈七,模型評(píng)估秒拔,超參數(shù)優(yōu)化等。

線性回歸和感知機(jī)分類

線性回歸模型

線性模型有最小二乘回歸飒硅、感知機(jī)砂缩、邏輯回歸作谚、嶺回歸,貝葉斯回歸等庵芭,由 sklearn.linear_model 模塊導(dǎo)入妹懒。對(duì)于線性模型而言,即通過擬合線性函數(shù)(下圖)去完成樣本分類或回歸預(yù)測双吆。


image.png

其中眨唬,最小二乘回歸、嶺回歸好乐、貝葉斯回歸等是用于解決回歸問題匾竿。而感知機(jī)、邏輯回歸被用于解決分類問題蔚万。
最小二乘法是線性回歸中最經(jīng)典的方法之一岭妖,最小二乘的取名即來自于其選擇了平方損失函數(shù)。在 scikit-learn 中反璃,最小二乘法的實(shí)現(xiàn)方法如下:


image.png

使用 scikit-learn 去解決一個(gè)機(jī)器學(xué)習(xí)相關(guān)的問題時(shí)昵慌,我們的代碼都大同小異,主要是由幾個(gè)部分組成:
調(diào)用一個(gè)機(jī)器學(xué)習(xí)方法構(gòu)建相應(yīng)的模型 model版扩,并設(shè)置模型參數(shù)废离。

使用該機(jī)器學(xué)習(xí)模型提供的 model.fit() 方法訓(xùn)練模型。
使用該機(jī)器學(xué)習(xí)模型提供的 model.predict() 方法用于預(yù)測礁芦。

通過最小二乘回歸去擬合二維平面上的一些點(diǎn)蜻韭。首先,執(zhí)行第一步柿扣,載入方法并構(gòu)建模型肖方。

import warnings
from sklearn.linear_model import LinearRegression

# 忽略代碼警告,僅供教學(xué)方便未状,自行書寫代碼時(shí)警告也很重要俯画,不建議忽略
warnings.filterwarnings('ignore')

model = LinearRegression()  # 調(diào)用最小二乘法線性回歸(第 1 步)
model

model 模型輸出的參數(shù),即為相應(yīng)算法類的默認(rèn)參數(shù)司草。

LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

使用模型帶有的 fit() 方法去擬合 3 個(gè)點(diǎn)艰垂。三個(gè)點(diǎn)的特征向量分別為[0,0], [1,1],[2,2],對(duì)應(yīng)的目標(biāo)值為[1,2,3]埋虹。

model.fit([[0, 0], [1, 1], [2, 2]], [1, 2, 3])  # 模型訓(xùn)練(第 2 步)

訓(xùn)練時(shí)猜憎,選擇的[0,0],[1,1]搔课,[2,2] 這三個(gè)點(diǎn)恰好在一條直線上胰柑,再結(jié)合目標(biāo)值想象一下它們的空間位置關(guān)系。我們可以使用下面的方法,輸出擬合直線 ww 項(xiàng)和常數(shù)項(xiàng)值柬讨。

model.coef_, model.intercept_

當(dāng)我們輸入新的數(shù)值崩瓤,例如[3,3] 時(shí),根據(jù)上面的函數(shù)踩官,因變量的值為 44却桶。那么,我們使用模型來預(yù)測蔗牡,看一看結(jié)果是否為 4肾扰。

model.predict([[3, 3]])  # 模型預(yù)測(第 3 步)

下面我們導(dǎo)入 scikit-learn 內(nèi)置的 diabetes 糖尿病數(shù)據(jù)集來訓(xùn)練一個(gè)復(fù)雜一點(diǎn)的最小二乘回歸模型。
第一步:導(dǎo)入數(shù)據(jù)蛋逾,并將其劃分為 70% 的訓(xùn)練集和 30% 的測試集。機(jī)器學(xué)習(xí)中窗悯,我們習(xí)慣采用這樣的比例來劃分訓(xùn)練集和測試集区匣。其中訓(xùn)練集用來訓(xùn)練模型,而測試集則用來評(píng)估模型的質(zhì)量蒋院。測試集的數(shù)據(jù)不會(huì)出現(xiàn)在訓(xùn)練數(shù)據(jù)中亏钩,這也就類似我們使用了新的數(shù)據(jù)對(duì)訓(xùn)練好的模型進(jìn)行預(yù)測和評(píng)估,以保證模型質(zhì)量真實(shí)可靠欺旧。


image.png
from sklearn import datasets  # 導(dǎo)入內(nèi)置數(shù)據(jù)集模塊
from sklearn.model_selection import train_test_split  # 導(dǎo)入數(shù)據(jù)集切分模塊
import numpy as np  # 導(dǎo)入數(shù)值計(jì)算模塊

diabetes = datasets.load_diabetes()  # 載入糖尿病數(shù)據(jù)集

diabetes_feature = diabetes.data[:, np.newaxis, 2]  # 該數(shù)據(jù)集的特征較多姑丑,這里只選取其中一個(gè)
diabetes_target = diabetes.target  # 設(shè)定目標(biāo)值

# 切分?jǐn)?shù)據(jù)集為 70% 的訓(xùn)練集和 30% 的預(yù)測集
# random_state 隨機(jī)數(shù)種子用于保證每次執(zhí)行結(jié)果一致
train_feature, test_feature, train_target, test_target = train_test_split(
    diabetes_feature, diabetes_target, test_size=0.3, random_state=56)

第二步:載入最小二乘回歸模型,并訓(xùn)練數(shù)據(jù)辞友。

model = LinearRegression()  # 構(gòu)建最小二乘線性回歸模型
model.fit(train_feature, train_target)  # 使用訓(xùn)練集數(shù)據(jù)訓(xùn)練模型

第三步:使用測試集進(jìn)行預(yù)測栅哀,并將結(jié)果繪圖。

import matplotlib.pyplot as plt  # 導(dǎo)入 matplotlib 繪圖模塊
%matplotlib inline

# 繪圖
plt.scatter(train_feature, train_target,  color='black')  # 繪制訓(xùn)練集散點(diǎn)圖
plt.scatter(test_feature, test_target,  color='red')  # 繪制測試集散點(diǎn)圖
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')

最后称龙,我們可以通過繪制的圖像留拾,更加直觀地看出采用最小二乘回歸模型進(jìn)行線性擬合的結(jié)果。


image.png

對(duì)于其他常見的線性回歸模型鲫尊,它們和最小二乘線性回歸模型非常相似痴柔,只是采用了不同的損失函數(shù)。
例如疫向,嶺回歸采用了帶 L2 懲罰項(xiàng)的平方和損失函數(shù)咳蔚。
而另一種常見的 Lasso 回歸,同樣采用了帶 L1 懲罰項(xiàng)的平方損失函數(shù)搔驼。
一些常見的廣義線性回歸模型谈火,及它們在 scikit-learn 中對(duì)應(yīng)的方法。


image.png

這些方法相對(duì)于普通最小二乘回歸模型而言匙奴,均增加了一些懲罰項(xiàng)堆巧。這樣會(huì)提高模型的泛化能力,在實(shí)際應(yīng)用中效果可能會(huì)好一些。

線性分類模型

感知機(jī) 是一個(gè)經(jīng)典的二分類方法谍肤,它是神經(jīng)網(wǎng)絡(luò)和支持向量機(jī)的基礎(chǔ)啦租。感知機(jī)模型非常簡單,輸入為一些特征向量荒揣,輸出則由正類和負(fù)類組成篷角。而輸入和輸出之間,則是由符號(hào)函數(shù)連接系任。
感知機(jī)的損失函數(shù)是錯(cuò)誤分類點(diǎn)到分離超平面之間的距離總和恳蹲,其學(xué)習(xí)策略同樣也是損失函數(shù)最小化。

image.png

實(shí)現(xiàn)感知機(jī)通過調(diào)用 sklearn.linear_model.Perceptron()方法完成俩滥。
首先嘉蕾,使用 scikit-learn 提供的 make_classification 方法生成一組可被二分類的二維數(shù)組作為數(shù)據(jù)集。

from sklearn.datasets import make_classification  # 導(dǎo)入分類數(shù)據(jù)生成模塊

# 隨機(jī)生成一組可以被二分類的數(shù)據(jù)
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  # 查看數(shù)組形狀

我們可以使用 Matplotlib 將該數(shù)據(jù)集繪制出來霜旧。

plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)  # 繪制數(shù)據(jù)集散點(diǎn)圖

其中错忱,y 即相當(dāng)于人為給數(shù)據(jù)添加的標(biāo)簽。
數(shù)據(jù)集分為 2 種顏色的樣本點(diǎn)挂据,并呈現(xiàn)出明顯的線性界線以清。接下來,我們使用感知機(jī)對(duì)該數(shù)據(jù)集進(jìn)行分類訓(xùn)練崎逃。

from sklearn.linear_model import Perceptron

# 將數(shù)據(jù)集劃分為 70% 訓(xùn)練集和 30% 測試集
train_feature, test_feature, train_target, test_target = train_test_split(
    X, y, test_size=0.3, random_state=56)
# 建立感知機(jī)模型掷倔,使用默認(rèn)參數(shù)
model = Perceptron()
# 使用訓(xùn)練集訓(xùn)練模型
model.fit(train_feature, train_target)

訓(xùn)練結(jié)束后,我們用測試數(shù)據(jù)進(jìn)行預(yù)測个绍。請(qǐng)注意勒葱,由于測試數(shù)據(jù)與訓(xùn)練數(shù)據(jù)完全不同,也是算法之前完全沒有見過的數(shù)據(jù)障贸。我們后續(xù)可以通過模型對(duì)測試數(shù)據(jù)的預(yù)測結(jié)果错森,然后與真實(shí)的結(jié)果進(jìn)行比較,從而得到模型的分類準(zhǔn)確度篮洁。

preds = model.predict(test_feature)  # 使用測試集預(yù)測

準(zhǔn)確度表示正確預(yù)測的樣本占全部樣本的比例涩维,是用于評(píng)估分類模型的常用指標(biāo)之一,我們得到的 preds 是模型的預(yù)測結(jié)果袁波,而真實(shí)結(jié)果為 test_target瓦阐。接下來,可以通過 scikit-learn 提供的 accuracy_score 計(jì)算出分類準(zhǔn)確度篷牌。

from sklearn.metrics import accuracy_score

accuracy_score(test_target, preds)  # 先傳入真實(shí)值睡蟋,再傳入預(yù)測值

返回的結(jié)果即是測試集預(yù)測分類準(zhǔn)確度,如果為 1.0 則表示預(yù)測全部正確枷颊,分類準(zhǔn)確度為 100%戳杀。
我們使用 Matplotlib 將訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)繪制在原圖上该面,并繪制出感知機(jī)分類時(shí)的決策邊界。

# 創(chuàng)建一個(gè)繪圖矩陣方便顯示決策邊界線
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)
# 繪制訓(xùn)練和測試數(shù)據(jù)
ax.scatter(train_feature[:, 0], train_feature[:, 1])
ax.scatter(test_feature[:, 0], test_feature[:, 1])

支持向量機(jī)分類預(yù)測

線性支持向量機(jī)

感知機(jī)的學(xué)習(xí)過程由誤分類驅(qū)動(dòng)信卡,即當(dāng)感知機(jī)尋找到?jīng)]有實(shí)例被錯(cuò)誤分類時(shí)隔缀,就確定了分割超平面。這樣雖然可以解決一些二分類問題傍菇,但是訓(xùn)練出來的模型往往容易出現(xiàn)過擬合猾瘸。


image.png

如上圖所示,當(dāng)感知機(jī)在進(jìn)行分類時(shí)丢习,為了照顧左下角的兩個(gè)紅色標(biāo)記樣本牵触,分割線會(huì)呈現(xiàn)出如圖所示的走向。你應(yīng)該通過觀察就能發(fā)現(xiàn)咐低,這條分割線不是特別合理揽思。
支持向量機(jī)也被看成是感知機(jī)的延伸。簡單來講见擦,支持向量機(jī)就是通過找出一個(gè)最大間隔超平面來完成分類绰更。


image.png

如圖所示,中間的實(shí)線是我們找到的分割超平面锡宋。這個(gè)超平面并不是隨手一畫,它必須滿足兩個(gè)類別中距離直線最近的樣本點(diǎn)特恬,與實(shí)線的距離一樣且最大执俩。這里的最大,也就是上面提到的最大間隔超平面癌刽。
支持向量機(jī)中的「支持向量」指的是上圖中役首,距離分割超平面最近的樣本點(diǎn),即兩條虛線上的一個(gè)實(shí)心點(diǎn)和兩個(gè)空心點(diǎn)显拜。

首先衡奥,我們需要先導(dǎo)入數(shù)據(jù)文件

import pandas as pd  # 導(dǎo)入 pandas 模塊
import warnings
warnings.filterwarnings('ignore')

# 讀取 csv 數(shù)據(jù)文件
df = pd.read_csv(
    "https://labfile.oss.aliyuncs.com/courses/866/data.csv", header=0)
df.head()

可以直接通過 df.head() 語句查看一下這個(gè)數(shù)據(jù)集頭部,對(duì)里面的數(shù)據(jù)組成初步熟悉一下远荠。
可以看到矮固,有兩個(gè)類別。其中 0 即表示上面圖中的藍(lán)色樣本點(diǎn)譬淳,1 對(duì)應(yīng)著紅色樣本點(diǎn)档址。
將整個(gè)數(shù)據(jù)集劃分為訓(xùn)練集和測試集兩部分,其中訓(xùn)練集占 70%邻梆。

from sklearn.model_selection import train_test_split  # 導(dǎo)入數(shù)據(jù)集劃分模塊

# 讀取特征值及目標(biāo)值
feature = df[["x", "y"]]
target = df["class"]

# 對(duì)數(shù)據(jù)集進(jìn)行分割
train_feature, test_feature, train_target, test_target = train_test_split(
    feature, target, test_size=0.3)

導(dǎo)入線性支持向量機(jī)分類器

from sklearn.svm import LinearSVC  # 導(dǎo)入線性支持向量機(jī)分類器

# 構(gòu)建線性支持向量機(jī)分類模型
model_svc = LinearSVC()
model_svc.fit(train_feature, train_target)

對(duì)模型在測試集上的準(zhǔn)確度進(jìn)行評(píng)估守伸,之前使用了 accuracy_score ,這里使用模型帶有的 score 方法效果是一樣的

# 支持向量機(jī)分類準(zhǔn)確度
model_svc.score(test_feature, test_target)

繪制出分類器的決策邊界

import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline

# 創(chuàng)建一個(gè)繪圖矩陣方便顯示決策邊界線
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)
# 繪制訓(xùn)練和測試數(shù)據(jù)
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)

非線性支持向量機(jī)

在實(shí)際生活中浦妄,我們大部分情況面對(duì)的卻是非線性分類問題尼摹,因?yàn)閷?shí)際數(shù)據(jù)往往都不會(huì)讓你通過一個(gè)水平超平面就能完美分類见芹。


image.png

上圖展現(xiàn)的就是一個(gè)非線性分類問題,而支持向量機(jī)就是解決非線性分類的有力武器蠢涝。
支持向量機(jī)引入了核函數(shù)來解決非線性分類的問題玄呛。簡單來講,通過核函數(shù)惠赫,我們可以將特征向量映射到高維空間中他爸,然后再高維空間中找到最大間隔分割超平面完成分類。而映射到高維空間這一步驟也相當(dāng)于將非線性分類問題轉(zhuǎn)化為線性分類問題肯污。


image.png

第一張圖中性置,紅藍(lán)球無法進(jìn)行線性分類。
使用核函數(shù)將特征映射到高維空間混埠,類似于在桌子上拍一巴掌使小球都飛起來了怠缸。

在高維空間完成線性分類后,再將超平面重新投影到原空間钳宪。

在將特征映射到高維空間的過程中揭北,我們常常會(huì)用到多種核函數(shù),包括:線性核函數(shù)吏颖、多項(xiàng)式核函數(shù)搔体、高斯徑向基核函數(shù)等。其中半醉,最常用的就算是高斯徑向基核函數(shù)了疚俱,也簡稱為 RBF 核。
我們選擇了 digits 手寫數(shù)字?jǐn)?shù)據(jù)集缩多。digits 數(shù)據(jù)集無需通過外部下載呆奕,可以直接由 scikit-learn 提供的 datasets.load_digits() 方法導(dǎo)入。該數(shù)據(jù)集的詳細(xì)信息如下:


image.png

第一步衬吆,導(dǎo)入數(shù)據(jù)并進(jìn)行初步觀察梁钾。

from sklearn import datasets  # 導(dǎo)入數(shù)據(jù)集模塊

# 載入數(shù)據(jù)集
digits = datasets.load_digits()

# 繪制數(shù)據(jù)集前 5 個(gè)手寫數(shù)字的灰度圖
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] 查看前五張手寫數(shù)字對(duì)應(yīng)的實(shí)際標(biāo)簽

digits.target[:5]

通常,我們在處理圖像問題時(shí)逊抡,都是將圖像的每一個(gè)像素轉(zhuǎn)換為灰度值或按比例縮放的灰度值姆泻。有了數(shù)值,就可以構(gòu)建和圖像像素大小相同的矩陣了冒嫡。在這里麦射,digits 已經(jīng)預(yù)置了每一張圖像對(duì)應(yīng)的矩陣,并包含在 digits.images 方法中灯谣。
我們可以通過 digits.images[1] 輸出第 1 張手寫數(shù)字對(duì)應(yīng)的 8x8 矩陣潜秋。很方便地,scikit-learn 已經(jīng)將 8x8 矩陣轉(zhuǎn)換成了方便作為特征變量輸入 64x1 的矩陣胎许,并放在了 digits.data 中峻呛。你可以使用 digits.data[1]查看罗售。

digits.data[1]

劃分訓(xùn)練集和測試集,然后針對(duì)測試集進(jìn)行預(yù)測并評(píng)估預(yù)測精準(zhǔn)度

from sklearn.svm import SVC  # 導(dǎo)入非線性支持向量機(jī)分類器
from sklearn.metrics import accuracy_score  # 導(dǎo)入評(píng)估模塊

feature = digits.data  # 指定特征
target = digits.target  # 指定目標(biāo)值

# 劃分?jǐn)?shù)據(jù)集钩述,將其中 70% 劃為訓(xùn)練集寨躁,另 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)  # 模型訓(xùn)練
results = model.predict(test_feature)  # 模型預(yù)測

scores = accuracy_score(test_target, results)  # 評(píng)估預(yù)測精準(zhǔn)度
scores

最后,模型預(yù)測準(zhǔn)確度為 44.6%牙勘。由于每一次運(yùn)行時(shí)职恳,數(shù)據(jù)集都會(huì)被重新劃分,所以你訓(xùn)練的準(zhǔn)確度甚至?xí)陀?44.6%方面。
我們在建立模型的時(shí)候使用的是默認(rèn)參數(shù)
該模型的確使用了最常用的 RBF 高斯徑向基核函數(shù)放钦,這沒有問題。問題出在了 gamma 參數(shù)恭金,gamma 是核函數(shù)的因數(shù)操禀,這里選擇了 auto 自動(dòng)。自動(dòng)即表示 gamma 的取值為 1 / 特征數(shù)量横腿,這里為 1/64颓屑。
嘗試將 gamma 參數(shù)的值改的更小一些,比如 0.001耿焊。重新建立模型

model = SVC(gamma=0.001)  # 重新建立模型
model.fit(train_feature, train_target)  # 模型訓(xùn)練
results = model.predict(test_feature)  # 模型預(yù)測

scores = accuracy_score(test_target, results)  # 評(píng)估預(yù)測精準(zhǔn)度
scores

可以看到揪惦,這一次的預(yù)測準(zhǔn)確度已經(jīng)達(dá)到 98% 了,結(jié)果非常理想罗侯。所以說丹擎,會(huì)用 scikit-learn 建立模型只是機(jī)器學(xué)習(xí)過程中最基礎(chǔ)的一步,更加重要的是理解模型的參數(shù)歇父,并學(xué)會(huì)調(diào)參使得模型的預(yù)測性能更優(yōu)。

監(jiān)督學(xué)習(xí)算法對(duì)比評(píng)估

K 近鄰

K 近鄰是一種十分常用的監(jiān)督學(xué)習(xí)算法再愈。簡單來講榜苫,K 近鄰就是假設(shè)一個(gè)給定的數(shù)據(jù)集,且數(shù)據(jù)的類別已經(jīng)確定翎冲。這些數(shù)據(jù)的特征所構(gòu)成的特征向量可以映射到對(duì)應(yīng)的特征空間中〈共牵現(xiàn)在,假設(shè)一個(gè)輸入實(shí)例抗悍,我們可以計(jì)算該輸入和其他數(shù)據(jù)點(diǎn)之間的距離驹饺,再通過多數(shù)表決的方式,來確定新輸入實(shí)例的類別缴渊,最后完成分類赏壹。
其中,K 近鄰中的「近鄰」代表原有特征空間中與新輸入實(shí)例距離最近的那些樣本衔沼。而 K 代表距離最近的 K 個(gè)樣本蝌借。所以昔瞧,對(duì)于 K 近鄰而言。K 值的大小和距離的度量方式(歐式距離或者曼哈頓距離)是其構(gòu)成的兩個(gè)關(guān)鍵因素菩佑。


image.png

如上圖所示自晰,原數(shù)據(jù)集由紅、綠兩類組成∩耘鳎現(xiàn)在酬荞,我們新輸入一個(gè)橙色實(shí)例,圖中表示了樣本對(duì)應(yīng)在特征空間的位置∏朴矗現(xiàn)在混巧,我們確定 K = 3,然后可以圈定出距離橙色實(shí)例最近的 3 個(gè)樣本點(diǎn)绢涡。其中牲剃,紅色樣本為 1 個(gè),綠色 2 個(gè)雄可。根據(jù)多數(shù)表決的規(guī)則凿傅,最終確定新輸入的橙色樣本數(shù)據(jù)被判定為 B 類別。而當(dāng)我們指定 K = 7 時(shí)数苫,紅色樣本 4 個(gè)聪舒,綠色樣本 3 個(gè),則橙色樣本被判定為 A 類別虐急。

決策樹和隨機(jī)森林

決策樹也是一種十分常見的監(jiān)督學(xué)習(xí)方法箱残。它是一種特殊的樹形結(jié)構(gòu),一般由節(jié)點(diǎn)和有向邊組成止吁。其中被辑,節(jié)點(diǎn)表示特征、屬性或者一個(gè)類敬惦。而有向邊包含有判斷條件盼理。


image.png

如圖所示,決策樹從根節(jié)點(diǎn)開始延伸俄删,經(jīng)過不同的判斷條件后宏怔,到達(dá)不同的子節(jié)點(diǎn)。而上層子節(jié)點(diǎn)又可以作為父節(jié)點(diǎn)被進(jìn)一步劃分為下層子節(jié)點(diǎn)畴椰。一般情況下臊诊,我們從根節(jié)點(diǎn)輸入數(shù)據(jù),經(jīng)過多次判斷后斜脂,這些數(shù)據(jù)就會(huì)被分為不同的類別抓艳。這就構(gòu)成了一顆簡單的分類決策樹。
當(dāng)我們使用決策樹分類時(shí)帚戳,對(duì)于已有訓(xùn)練集只建立一顆決策樹壶硅。而隨機(jī)森林的概念是威兜,對(duì)于一個(gè)訓(xùn)練集隨機(jī)建立多顆決策樹。而建立這些決策樹時(shí)庐椒,會(huì)采取一種叫 Bootstrap 的取樣方式椒舵,即每一次從數(shù)據(jù)集中又放回的取出一部分?jǐn)?shù)據(jù),再用這部分?jǐn)?shù)據(jù)去建立小決策樹约谈。對(duì)于隨機(jī)森林而言笔宿,最終的分類結(jié)果由眾多小決策樹輸出類別的眾數(shù)確定。下圖展示了一個(gè)由 3 顆決策樹構(gòu)成的隨機(jī)森林過程棱诱。


image.png

由于隨機(jī)森林的特點(diǎn)泼橘,有效地降低過擬合程度,具有較好的泛化誤差迈勋。另外炬灭,訓(xùn)練速度也非常快靡菇,模型的表現(xiàn)往往都比較好重归,是十分受歡迎的一種機(jī)器學(xué)習(xí)方法。

常見監(jiān)督學(xué)習(xí)方法

通過同一個(gè)示例數(shù)據(jù)集來對(duì)常見的監(jiān)督學(xué)習(xí)算法分類性能做一個(gè)比較厦凤。
為了更方便可視化鼻吮,這里選用了一個(gè)隨機(jī)生成的二分類數(shù)據(jù)集〗瞎模總共包含 300 條數(shù)據(jù)椎木,類別為 0 和 1。

import pandas as pd  # 加載 pandas 模塊
import warnings
warnings.filterwarnings('ignore')

# 讀取 csv 文件, 并將第一行設(shè)為表頭
data = pd.read_csv(
    "https://labfile.oss.aliyuncs.com/courses/866/class_data.csv", header=0)

data.head()  # 輸出數(shù)據(jù)預(yù)覽

通常情況下博烂,可視化是直觀認(rèn)識(shí)陌生數(shù)據(jù)的很好方法香椎。這里,我們通過 Matplotlib 來可視化這些數(shù)據(jù)禽篱。畫出數(shù)據(jù)集的散點(diǎn)圖畜伐。

from matplotlib import pyplot as plt  # 加載繪圖模塊
%matplotlib inline

plt.scatter(data["X"], data['Y'], c=data['CLASS'])  # 繪制散點(diǎn)圖

我們用 c=data['CLASS'] 參數(shù)來控制散點(diǎn)的顏色。
接下來谆级,加載本次實(shí)驗(yàn)需要的模塊,以及 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
# 神經(jīng)網(wǎng)絡(luò)分類器
from sklearn.neural_network import MLPClassifier
# 決策樹分類器
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import ExtraTreeClassifier
# 支持向量機(jī)分類器
from sklearn.svm import SVC
from sklearn.svm import LinearSVC

接下來肥照,建立預(yù)測模型,采用默認(rèn)參數(shù)即可勤众。由于方法較多舆绎,所有這里就不再依次單獨(dú)定義模型,而是用列表形式管理们颜。

# 建立模型
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']

然后吕朵,劃分?jǐn)?shù)據(jù)集猎醇。70% 用于訓(xùn)練,另外 30% 用于測試努溃。

from sklearn.model_selection import train_test_split  # 導(dǎo)入數(shù)據(jù)集切分模塊

feature = data[['X', 'Y']]  # 指定特征變量
target = data['CLASS']  # 指定標(biāo)簽變量
X_train, X_test, y_train, y_test = train_test_split(
    feature, target, test_size=.3)  # 切分?jǐn)?shù)據(jù)集

準(zhǔn)備好數(shù)據(jù)之后硫嘶,就可以開始模型訓(xùn)練和測試了。

from sklearn.metrics import accuracy_score  # 導(dǎo)入準(zhǔn)確度評(píng)估模塊

# 遍歷所有模型
for name, model in zip(classifier_Names, models):
    model.fit(X_train, y_train)  # 訓(xùn)練模型
    pre_labels = model.predict(X_test)  # 模型預(yù)測
    score = accuracy_score(y_test, pre_labels)  # 計(jì)算預(yù)測準(zhǔn)確度
    print('%s: %.2f' % (name, score))  # 輸出模型準(zhǔn)確度

這 16 個(gè)分類器最終的準(zhǔn)確度均在 80% ~ 90% 之間梧税,差距不是很大沦疾。對(duì)于這種現(xiàn)象,主要有兩個(gè)原因第队。首先哮塞,本次使用的是一個(gè)非常規(guī)范整潔的線性分類數(shù)據(jù)集。其次凳谦,所有的分類器均采用了默認(rèn)參數(shù)忆畅,而 scikit-learn 提供的默認(rèn)參數(shù)一般已經(jīng)較優(yōu)。
我們通過可視化的方法將每一個(gè)模型在分類時(shí)的決策邊界展示出來尸执,這樣能更加直觀的感受到機(jī)器學(xué)習(xí)模型在執(zhí)行分類預(yù)測時(shí)發(fā)生的變化家凯。

from matplotlib.colors import ListedColormap  # 加載色彩模塊
import numpy as np  # 導(dǎo)入數(shù)值計(jì)算模塊
from tqdm.notebook import tqdm

# 繪制數(shù)據(jù)集
i = 1  # 為繪制子圖設(shè)置的初始編號(hào)參數(shù)
cm = plt.cm.Reds  # 為繪制等高線選擇的樣式
cm_color = ListedColormap(['red', 'yellow'])  # 為繪制訓(xùn)練集和測試集選擇的樣式

# 柵格化
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)  # 模型訓(xùn)練
    pre_labels = model.predict(X_test)  # 模型測試
    score = accuracy_score(y_test, pre_labels)  # 模型準(zhǔn)確度

    # 根據(jù)類的不同選擇決策邊界計(jì)算方法
    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)

    # 繪制訓(xùn)練集和測試集
    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')

    # 圖形樣式設(shè)定
    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

按照上面的步驟執(zhí)行,就能看到如下圖所示的對(duì)比圖了剔交。


image.png

上面將決策邊界繪制出來肆饶,并用等高線圖顯示。其中岖常,顏色越深表示偏向于黃色散點(diǎn)分類的概率越高驯镊,而顏色越淺,則表示偏向紅色散點(diǎn)的概率越高竭鞍。
為了進(jìn)一步探索各分類器對(duì)于不同特征分布的數(shù)據(jù)集的適用情況板惑。接下來,我們將原有數(shù)據(jù)集做一些變換偎快。
sklearn.datasets 這個(gè)模塊可以導(dǎo)入一些預(yù)設(shè)的數(shù)據(jù)集冯乘。其實(shí),不僅如此晒夹,這個(gè)模塊開提供了一些數(shù)據(jù)集的生成方法裆馒。比如:
sklearn.datasets.make_circles 方法可以生成大圓環(huán)包小圓環(huán)樣式的數(shù)據(jù)集。
sklearn.datasets.make_moons 方法可以生成兩個(gè)交織間隔圓環(huán)樣式的數(shù)據(jù)集丐怯。

from sklearn import datasets

# 生成 200 個(gè)包含噪聲的環(huán)狀樣本
circles = datasets.make_circles(n_samples=200, noise=.1)
plt.scatter(circles[0][:, 0], circles[0][:, 1], c=circles[1])

# 生成 300 個(gè)包含噪聲的月牙狀樣本
moons = datasets.make_moons(n_samples=300, noise=.2, random_state=1)
plt.scatter(moons[0][:, 0], moons[0][:, 1], c=moons[1])

這兩組數(shù)據(jù)都是無法進(jìn)行線性分類喷好,所以如果是非線性分類器,其結(jié)果應(yīng)該會(huì)好很多读跷。

# 對(duì)月牙狀樣本進(jìn)行分割測試
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)  # 訓(xùn)練模型
    pre_labels = model.predict(X_test)  # 模型預(yù)測
    score = accuracy_score(y_test, pre_labels)  # 計(jì)算預(yù)測準(zhǔn)確度
    print('%s: %.2f' % (name, score))  # 輸出模型準(zhǔn)確度

K-Means 數(shù)據(jù)聚類算法應(yīng)用

K-Means 聚類

監(jiān)督學(xué)習(xí)被用于解決分類和回歸問題梗搅,而非監(jiān)督學(xué)習(xí)主要是用于解決聚類問題。聚類,顧名思義就是將具有相似屬性或特征的數(shù)據(jù)聚合在一起无切。聚類算法有很多荡短,最簡單和最常用的就算是 K-Means 算法了。
K-Means哆键,中文譯作 K - 均值算法掘托。從它的名字來講,K 代表最終將樣本數(shù)據(jù)聚合為 K 個(gè)類別洼哎。而「均值」代表在聚類的過程中烫映,我們計(jì)算聚類中心點(diǎn)的特征向量時(shí),需要采用求相鄰樣本點(diǎn)特征向量均值的方式進(jìn)行噩峦。例如:


image.png

K-Means 算法在應(yīng)用時(shí)锭沟,相對(duì)來上面的例子要復(fù)雜一些。現(xiàn)在识补,假設(shè)有如下圖所示的一組二維數(shù)據(jù)族淮。接下來,我們就一步一步演示 K-Means 的聚類過程凭涂。
首先祝辣,正如我們前面介紹非監(jiān)督學(xué)習(xí)時(shí)所說,非監(jiān)督學(xué)習(xí)面對(duì)的數(shù)據(jù)都是沒有標(biāo)簽的切油。如果我們把下方示例數(shù)據(jù)的顏色看作是標(biāo)簽蝙斜,那么只有一種顏色。


image.png

第一步澎胡,確定要聚為幾類孕荠?也就是 K 值。假設(shè)攻谁,這里我們想將樣本聚為 3 類稚伍。當(dāng)然,你也完全可以將其聚為 2 類或 4 類戚宦,不要受到視覺上的誤導(dǎo)个曙。
這里,我們以 3 類為例受楼。當(dāng)確定聚為 3 類之后垦搬,我們在特征空間上,隨機(jī)初始化 3 個(gè)類別中心艳汽。這里的 3 也就對(duì)應(yīng)著 K 值的大小猴贰。
image.png

第二步,我們依據(jù)這三個(gè)隨機(jī)初始化的中心骚灸,將現(xiàn)有樣本按照與最近中心點(diǎn)之間的距離進(jìn)行歸類糟趾。圖中綠線將全部樣本劃分為三個(gè)類別慌植。(中間小三角形是參考線甚牲,可以忽略义郑。)


image.png

這樣,我們的樣本被劃為為三個(gè)區(qū)域≌筛疲現(xiàn)在非驮,我們就要用到上面提到的均值來重新求解 3 個(gè)區(qū)域?qū)?yīng)的新的樣本中心。
image.png

如上圖所示雏赦,假設(shè)我們求解的新樣本中心為三個(gè)綠點(diǎn)所示的位置劫笙。然后,又重新回到上一步星岗,根據(jù)這三個(gè)中心重新劃分樣本填大,再求解中心。
image.png

依次迭代俏橘,直到樣本中心變化非常小時(shí)終止允华。最終,就可以將全部樣本聚類為三類寥掐。

首先靴寂,我們導(dǎo)入 Pandas 數(shù)據(jù)處理模塊,用來解析 CSV 數(shù)據(jù)文件召耘,并查看文件的組成結(jié)構(gòu)百炬。

import pandas as pd  # 導(dǎo)入數(shù)據(jù)處理模塊
import warnings
warnings.filterwarnings('ignore')

df = pd.read_csv(
    "http://labfile.oss.aliyuncs.com/courses/866/cluster_data.csv", header=0)  # 導(dǎo)入數(shù)據(jù)文件
df.head()

可以看到,文件包含兩列污它,也就是對(duì)應(yīng)在特征空間的 x, y 坐標(biāo)剖踊。接下來,我們用 Matplotlib 將數(shù)據(jù)繪制成散點(diǎn)圖轨蛤。

from matplotlib import pyplot as plt  # 導(dǎo)入繪圖模塊
%matplotlib inline

X = df['x']  # 定義橫坐標(biāo)數(shù)據(jù)
y = df['y']  # 定義縱坐標(biāo)數(shù)據(jù)

plt.scatter(X, y)  # 繪制散點(diǎn)圖

接下來蜜宪,開始導(dǎo)入 K-Means 方法進(jìn)行聚類。scikit-learn 中的聚類算法都包含在 sklearn.cluster 方法下祥山。

from sklearn.cluster import k_means  # 導(dǎo)入 K-Means 方法

model = k_means(df, n_clusters=3)  # 建立聚類模型
model

model 輸出的結(jié)果包含三個(gè)數(shù)組圃验。其中,第一個(gè)數(shù)組表示三個(gè)聚類中心點(diǎn)坐標(biāo)缝呕。第二個(gè)數(shù)組表示樣本聚類后類別澳窑,第三個(gè)數(shù)組表示樣本距最近聚類中心的距離總和。
接下來供常,我們就將聚類的結(jié)果繪制出來摊聋。

cluster_centers = model[0]  # 聚類中心數(shù)組
cluster_labels = model[1]  # 聚類標(biāo)簽數(shù)組

plt.scatter(X, y, c=cluster_labels)  # 繪制樣本并按聚類標(biāo)簽標(biāo)注顏色

# 繪制聚類中心點(diǎn),標(biāo)記成五角星樣式栈暇,以及紅色邊框
for center in cluster_centers:
    plt.scatter(center[0], center[1], marker="p", edgecolors="red")

可以看到麻裁,聚類的結(jié)果已經(jīng)顯示出來了,聚類中心也做了相應(yīng)標(biāo)記。

K 值選擇

我們?yōu)槭裁匆鄢?3 類煎源?為什么不可以將數(shù)據(jù)聚成 10 類色迂?
聚成 10 類當(dāng)然是可以的,只需要將 n_clusters=10 即可手销。

model = k_means(df, n_clusters=10)  # 建立聚類模型

cluster_centers = model[0]  # 聚類中心數(shù)組
cluster_labels = model[1]  # 聚類標(biāo)簽數(shù)組
plt.scatter(X, y, c=cluster_labels)  # 繪制樣本并按聚類標(biāo)簽標(biāo)注顏色

# 繪制聚類中心點(diǎn)歇僧,標(biāo)記成五角星樣式,以及紅色邊框
for center in cluster_centers:
    plt.scatter(center[0], center[1], marker="p", edgecolors="red")

那我們?yōu)槭裁磿?huì)選擇 K=3 呢锋拖?當(dāng)我們遇到肉眼看起來不太好確定類別诈悍,或者是高維數(shù)據(jù)時(shí)怎么辦呢?
所以兽埃,這一系列問題最終可以歸結(jié)為一個(gè)問題侥钳,那就是:當(dāng)我們在使用 K-Means 聚類時(shí),我們該如何確定 K 值柄错?
啟發(fā)式學(xué)習(xí)算法慕趴,被稱之為肘部法則。在上文談到用 print(model) 語句時(shí)鄙陡,它會(huì)輸出三個(gè)數(shù)組冕房。其中,前兩個(gè)數(shù)組在進(jìn)行聚類繪圖時(shí)已經(jīng)用到了趁矾,但是我們一直沒有使用第三個(gè)數(shù)組耙册。

model[2]  # 打印第三個(gè)數(shù)組

第三個(gè)數(shù)組,準(zhǔn)確說來只是一個(gè)數(shù)值毫捣。它代表著樣本與最近中心點(diǎn)距離的平方和详拙。當(dāng)我們的 K 值增加時(shí),也就是類別增加時(shí)蔓同,這個(gè)數(shù)值應(yīng)該是會(huì)降低的饶辙。直到聚類類別的數(shù)量和樣本的總數(shù)相同時(shí),也就是說一個(gè)樣本就代表一個(gè)類別時(shí)斑粱,這個(gè)數(shù)值會(huì)變成 0弃揽。

index = []  # 橫坐標(biāo)數(shù)組
inertia = []  # 縱坐標(biāo)數(shù)組

# 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")

我們可以看到,和預(yù)想的一樣则北,樣本距離最近中心點(diǎn)距離的總和會(huì)隨著 K 值的增大而降低矿微。其中,畸變程度最大的點(diǎn)被稱之為「肘部」尚揣。我們可以看到涌矢,這里的「肘部」明顯是 K = 3。這也說明快骗,將樣本聚為 3 類的確是最佳選擇娜庇。
當(dāng)我們完成一項(xiàng)聚類任務(wù)之后塔次,我們需要對(duì)聚類效果進(jìn)行評(píng)估。其實(shí)名秀,上面提到的肘部法則也算是一種評(píng)估方法俺叭,它讓我們知道當(dāng) K 值為多少時(shí),整體聚類的結(jié)果更理想泰偿。
聚類中還有一種評(píng)估方法,叫 輪廓系數(shù)蜈垮。輪廓系數(shù)綜合了聚類后的兩項(xiàng)因素:內(nèi)聚度和分離度耗跛。內(nèi)聚度就指一個(gè)樣本在簇內(nèi)的不相似度,而分離度就指一個(gè)樣本在簇間的不相似度攒发。
在 scikit-learn 種调塌,同樣也提供了直接計(jì)算輪廓系數(shù)的方法。下面惠猿,繪制出輪廓系數(shù)與聚類 K 值變化的折線圖羔砾。

from sklearn.metrics import silhouette_score  # 導(dǎo)入輪廓系數(shù)計(jì)算模塊

index2 = []  # 橫坐標(biāo)
silhouette = []  # 輪廓系數(shù)列表

# 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)  # 輸出不同聚類下的輪廓系數(shù)

# 繪制折線圖
plt.plot(index2, silhouette, "-o")

輪廓系數(shù)越接近于 1,代表聚類的效果越好偶妖。我們可以很清楚的看出姜凄,K=3 對(duì)應(yīng)的輪廓系數(shù)數(shù)組最大,也更接近于 1 趾访。

PCA 主成分分析應(yīng)用

主成分分析

主成分分析 是多元線性統(tǒng)計(jì)里面的概念态秧,它的英文是 Principal Components Analysis,簡稱 PCA扼鞋。主成分分析旨在降低數(shù)據(jù)的維數(shù)申鱼,通過保留數(shù)據(jù)集中的主要成分來簡化數(shù)據(jù)集。簡化數(shù)據(jù)集在很多時(shí)候是非常必要的云头,因?yàn)閺?fù)雜往往就意味著計(jì)算資源的大量消耗捐友。通過對(duì)數(shù)據(jù)進(jìn)行降維,我們就能在結(jié)果不受影響或受略微影響的同時(shí)溃槐,減少模型學(xué)習(xí)時(shí)間匣砖。
本次實(shí)驗(yàn)所說的降維,不是指降低 NumPy 數(shù)組的維度昏滴,而是特指減少樣本的特征數(shù)量脆粥。
主成分分析的數(shù)學(xué)基原理比較簡單,通過對(duì)協(xié)方差矩陣進(jìn)行特征分解影涉,從而得出主成分(特征向量)與對(duì)應(yīng)的權(quán)值(特征值)变隔。然后剔除那些較小特征值(較小權(quán)值)對(duì)應(yīng)的特征向量,從而達(dá)到降低數(shù)據(jù)維數(shù)的目的蟹倾。

image.png

上圖展示了把三維空間的數(shù)據(jù)降維到二維空間的過程匣缘。

sklearn.decomposition.PCA(n_components=None, copy=True, whiten=False, svd_solver='auto')

n_components= 表示需要保留主成分(特征)的數(shù)量猖闪。
copy= 表示針對(duì)原始數(shù)據(jù)降維還是針對(duì)原始數(shù)據(jù)副本降維。當(dāng)參數(shù)為 False 時(shí)肌厨,降維后的原始數(shù)據(jù)會(huì)發(fā)生改變培慌,這里默認(rèn)為 True。
whiten= 白化表示將特征之間的相關(guān)性降低柑爸,并使得每個(gè)特征具有相同的方差吵护。
svd_solver= 表示奇異值分解 SVD 的方法。有 4 參數(shù)表鳍,分別是:auto, full, arpack, randomized馅而。
在使用 PCA 降維時(shí),我們也會(huì)使用到 PCA.fit() 方法譬圣。.fit() 是 scikit-learn 訓(xùn)練模型的通用方法瓮恭,但是該方法本身返回的是模型的參數(shù)。所以厘熟,通常我們會(huì)使用 PCA.fit_transform() 方法直接返回降維后的數(shù)據(jù)結(jié)果屯蹦。

import numpy as np  # 導(dǎo)入數(shù)值計(jì)算模塊
from sklearn.decomposition import PCA  # 導(dǎo)入 PCA 模塊
import warnings
warnings.filterwarnings('ignore')

data = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])  # 新建一個(gè) 2 維數(shù)組
new_data = PCA(n_components=1).fit_transform(data)  # 降維成 1 維并返回值

print(data)  # 輸出原數(shù)據(jù)
print(new_data)  # 輸出降維后的數(shù)據(jù)

我們可以看到,原先包含 2 個(gè)特征的數(shù)組已經(jīng)降維為 1 組特征绳姨。

手寫數(shù)字識(shí)別聚類

手寫數(shù)字?jǐn)?shù)據(jù)集一共由 1797 張 8x8 的影像組成登澜。該數(shù)據(jù)集可以直接通過 scikit-learn 導(dǎo)入,無需到外部下載飘庄。
先輸出前 5 張圖像預(yù)覽一下

from sklearn import datasets  # 導(dǎo)入數(shù)據(jù)集模塊
import matplotlib.pyplot as plt  # 導(dǎo)入繪圖模塊
%matplotlib inline

# 載入數(shù)據(jù)集
digits_data = datasets.load_digits()

# 繪制數(shù)據(jù)集前 5 個(gè)手寫數(shù)字的灰度圖
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 張數(shù)字圖像依次為 0帖渠,1,2竭宰,3空郊,4。之前切揭,我們用支持向量機(jī)完成了分類狞甚,即預(yù)測哪一張圖像代表哪一個(gè)數(shù)字。現(xiàn)在廓旬,我們采用相同的數(shù)據(jù)集完成聚類分析哼审,即將全部數(shù)據(jù)集聚為 10 個(gè)類別。
首先孕豹,我們導(dǎo)入常用的 NumPy 數(shù)值計(jì)算模塊和 Matplotlib 繪圖模塊涩盾。由于原數(shù)據(jù)集維度達(dá)到 16,所以這里要進(jìn)行 PCA 降維励背。

from sklearn import decomposition
from sklearn.cluster import KMeans

# 導(dǎo)入數(shù)據(jù)集
digits_data = datasets.load_digits()
X = digits_data.data
y = digits_data.target

# PCA 將數(shù)據(jù)降為 2 維
estimator = decomposition.PCA(n_components=2)
reduce_data = estimator.fit_transform(X)
reduce_data.shape

接下來春霍,將降維后的數(shù)據(jù)聚為 10 類,并將聚類后的結(jié)果叶眉、聚類中心點(diǎn)址儒、聚類決策邊界繪制出來锭碳。

# 建立 K-Means 并輸入數(shù)據(jù)
model = KMeans(n_clusters=10)
model.fit(reduce_data)

# 計(jì)算聚類過程中的決策邊界
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)

# 繪制聚類中心點(diǎn)
center = model.cluster_centers_
plt.scatter(center[:, 0], center[:, 1], marker='p',
            linewidths=2, color='b', edgecolors='w', zorder=20)

# 圖像參數(shù)設(shè)置
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)

圖中面哥,不同的色塊區(qū)域代表一類亚斋。這里色塊的顏色沒有意義隙弛,只表示類別。散點(diǎn)代表數(shù)據(jù)喧伞,散點(diǎn)的顏色表示數(shù)據(jù)原始類別走芋。我們可以看出,雖然原始數(shù)據(jù)已經(jīng)從 16 維降維 2 維潘鲫,但某幾個(gè)數(shù)字的依舊有明顯的成團(tuán)現(xiàn)象翁逞。
除此之外,我們還可以使用分類方法來對(duì)比降維前后的數(shù)據(jù)表現(xiàn)次舌。我們使用隨機(jī)森林方法對(duì)數(shù)據(jù)進(jìn)行分類,并通過交叉驗(yàn)證得到準(zhǔn)確度結(jié)果兽愤。

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 折交叉驗(yàn)證平均準(zhǔn)確度

然后彼念,我們再使用降維后的數(shù)據(jù)做一次一樣的分類實(shí)驗(yàn)。

estimator = decomposition.PCA(n_components=5) # 從 10 個(gè)特征縮減為 5 個(gè)特征
X_pca = estimator.fit_transform(X)

model = RandomForestClassifier()
cross_val_score(model, X_pca, y, cv=5).mean()  # 5 折交叉驗(yàn)證平均準(zhǔn)確度

特征減少 1 半浅萧,實(shí)際上準(zhǔn)確度減少并不多逐沙。也就是說,在客觀條件限制下洼畅,我們往往可以以更少維度的數(shù)據(jù)訓(xùn)練出準(zhǔn)確度可以勉強(qiáng)接受的模型吩案。

聚類學(xué)習(xí)算法對(duì)比評(píng)估

更多聚類算法

Mini Batch K-Means
實(shí)現(xiàn)方法:sklearn.cluster.MiniBatchKMeans
Mini Batch K-Means 整體上和 K-Means 很相似,它是 K-Means 的一個(gè)變種形式帝簇。與 K-Means 不同的地方在于徘郭,其每次從全部數(shù)據(jù)集中抽樣小數(shù)據(jù)集進(jìn)行迭代。Mini Batch K-Means 算法在不對(duì)聚類效果造成較大影響的前提下丧肴,大大縮短了計(jì)算時(shí)間残揉。
Affinity Propagation
實(shí)現(xiàn)方法:sklearn.cluster.AffinityPropagation
Affinity Propagation 又被稱為親和傳播聚類。Affinity Propagation 是基于數(shù)據(jù)點(diǎn)進(jìn)行消息傳遞的理念設(shè)計(jì)的芋浮。與 K-Means 等聚類算法不同的地方在于抱环,親和傳播聚類不需要提前確定聚類的數(shù)量,即 K 值纸巷。但是運(yùn)行效率較低镇草。
Mean Shift
實(shí)現(xiàn)方法:sklearn.cluster.MeanShift
MeanShift 又被稱為均值漂移聚類。Mean Shift 聚類的目的是找出最密集的區(qū)域瘤旨, 同樣也是一個(gè)迭代過程梯啤。在聚類過程中,首先算出初始中心點(diǎn)的偏移均值存哲,將該點(diǎn)移動(dòng)到此偏移均值条辟,然后以此為新的起始點(diǎn)黔夭,繼續(xù)移動(dòng),直到滿足最終的條件羽嫡。Mean Shift 也引入了核函數(shù)本姥,用于改善聚類效果。除此之外杭棵,Mean Shift 在圖像分割婚惫,視頻跟蹤等領(lǐng)域也有較好的應(yīng)用。
Spectral Clustering
實(shí)現(xiàn)方法:sklearn.cluster.SpectralClustering
Spectral Clustering 又被稱為譜聚類魂爪。譜聚類同樣也是一種比較常見的聚類方法先舷,它是從圖論中演化而來的。譜聚類一開始將特征空間中的點(diǎn)用邊連接起來滓侍。其中蒋川,兩個(gè)點(diǎn)距離越遠(yuǎn),那么邊所對(duì)應(yīng)的權(quán)值越低撩笆。同樣捺球,距離越近,那么邊對(duì)應(yīng)的權(quán)值越高夕冲。最后氮兵,通過對(duì)所有特征點(diǎn)組成的網(wǎng)絡(luò)進(jìn)行切分,讓切分后的子圖互相連接的邊權(quán)重之和盡可能的低歹鱼,而各子圖內(nèi)部邊組成的權(quán)值和經(jīng)可能高泣栈,從而達(dá)到聚類的效果。譜聚類的好處是能夠識(shí)別任意形狀的樣本空間弥姻,并且可以得到全局最優(yōu)解南片。
Agglomerative Clustering
實(shí)現(xiàn)方法:sklearn.cluster.AgglomerativeClustering
Agglomerative Clustering 又被稱為層次聚類。層次聚類算法是將所有的樣本點(diǎn)自下而上合并組成一棵樹的過程庭敦,它不再產(chǎn)生單一聚類铃绒,而是產(chǎn)生一個(gè)聚類層次。層次聚類通過計(jì)算各樣本數(shù)據(jù)之間的距離來確定它們的相似性關(guān)系螺捐,一般情況下颠悬,距離越小就代表相似度越高。最后定血,將相似度越高的樣本歸為一類赔癌,依次迭代,直到生成一棵樹澜沟。由于層次聚類涉及到循環(huán)計(jì)算灾票,所以時(shí)間復(fù)雜度比較高,運(yùn)行速度較慢茫虽。
Birch 聚類
實(shí)現(xiàn)方法:sklearn.cluster.Birch
Birch 是英文 Balanced Iterative Reducing and Clustering Using Hierarchies 的簡稱刊苍,它的中文譯名為「基于層次方法的平衡迭代規(guī)約和聚類」既们,名字實(shí)在太長。
Birch 引入了聚類特征樹(CF 樹)正什,先通過其他的聚類方法將其聚類成小的簇啥纸,然后再在簇間采用 CF 樹對(duì)簇聚類。Birch 的優(yōu)點(diǎn)是婴氮,只需要單次掃描數(shù)據(jù)集即可完成聚類斯棒,運(yùn)行速度較快,特別適合大數(shù)據(jù)集主经。
DBSCAN
實(shí)現(xiàn)方法:sklearn.cluster.DBSCAN
DBSCAN 是英文 Density-based spatial clustering of applications with noise 的簡稱荣暮,它的中文譯名為「基于空間密度與噪聲應(yīng)用的聚類方法」,名字同樣很長罩驻。
DBSCAN 基于密度概念穗酥,要求聚類空間中的一定區(qū)域內(nèi)所包含的樣本數(shù)目不小于某一給定閾值。該算法運(yùn)行速度快惠遏,且能夠有效處理特征空間中存在的噪聲點(diǎn)砾跃。但是對(duì)于密度分布不均勻的樣本集合,DBSCAN 的表現(xiàn)較差爽哎。

聚類算法對(duì)比

首先蜓席,我們從 sklearn.cluster 模塊中器一,導(dǎo)入各聚類方法课锌。如 K-Means 等方法需要提前確定類別數(shù)量,也就是 K 值祈秕。判斷的方法很簡單渺贤,如果聚類方法中包含 n_clusters= 參數(shù),即代表需要提前指定请毛。這里我們統(tǒng)一確定 K=3志鞍。

from sklearn import cluster  # 導(dǎo)入聚類模塊

# 對(duì)聚類方法依次命名
cluster_names = ['KMeans', 'MiniBatchKMeans', 'AffinityPropagation', 'MeanShift',
                 'SpectralClustering', 'AgglomerativeClustering', 'Birch', 'DBSCAN']

# 確定聚類方法相應(yīng)參數(shù)
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()
]

接下來,我們對(duì)上面提到的 8 種常見的聚類算法做一個(gè)對(duì)比方仿。這里選擇了一個(gè)空間分布由三個(gè)團(tuán)狀圖案組成的示例數(shù)據(jù)集固棚。

import pandas as pd  # 導(dǎo)入數(shù)據(jù)處理模塊
from matplotlib import pyplot as plt  # 導(dǎo)入繪圖模塊
%matplotlib inline

# 讀取數(shù)據(jù)集 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'])

然后,我們分別應(yīng)用 8 種聚類方法對(duì)數(shù)據(jù)進(jìn)行聚類仙蚜,并將最終的聚類結(jié)果繪制出來此洲。

import numpy as np  # 導(dǎo)入數(shù)值計(jì)算模塊

plot_num = 1  # 為繪制子圖準(zhǔn)備
plt.figure(figsize=(20, 10))
# 不同的聚類方法依次運(yùn)行
for name, algorithm in zip(cluster_names, cluster_estimators):
    algorithm.fit(X)  # 聚類
    # 判斷方法中是否由 labels_ 參數(shù),并執(zhí)行不同的命令
    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_ 參數(shù)委粉,并執(zhí)行不同的命令
    if hasattr(algorithm, 'cluster_centers_'):
        centers = algorithm.cluster_centers_
        plt.scatter(centers[:, 0], centers[:, 1], marker="p", edgecolors="red")
    # 繪制圖標(biāo)題
    plt.title(name)
    plot_num += 1

在我們指定 n_clusters=3 的方法中呜师,除了 SpectralClustering 出現(xiàn)了三個(gè)樣本點(diǎn)漂移,其他幾種方法的結(jié)果幾乎是一致的贾节。除此之外汁汗,在沒有指定 n_clusters 的聚類方法中衷畦,Mean Shift 對(duì)于此數(shù)據(jù)集的適應(yīng)性較好,而親和傳播聚類方法在默認(rèn)參數(shù)下表現(xiàn)最差知牌。
scikit-learn 提供了一張選擇判斷圖供大家參考祈争。


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市送爸,隨后出現(xiàn)的幾起案子铛嘱,更是在濱河造成了極大的恐慌,老刑警劉巖袭厂,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墨吓,死亡現(xiàn)場離奇詭異,居然都是意外死亡纹磺,警方通過查閱死者的電腦和手機(jī)帖烘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橄杨,“玉大人秘症,你說我怎么就攤上這事∈浇茫” “怎么了乡摹?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長采转。 經(jīng)常有香客問我聪廉,道長,這世上最難降的妖魔是什么故慈? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任板熊,我火速辦了婚禮,結(jié)果婚禮上察绷,老公的妹妹穿的比我還像新娘干签。我一直安慰自己,他們只是感情好拆撼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布容劳。 她就那樣靜靜地躺著,像睡著了一般闸度。 火紅的嫁衣襯著肌膚如雪竭贩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天筋岛,我揣著相機(jī)與錄音娶视,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肪获,可吹牛的內(nèi)容都是我干的寝凌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼孝赫,長吁一口氣:“原來是場噩夢啊……” “哼较木!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起青柄,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤伐债,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后致开,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峰锁,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年双戳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虹蒋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飒货,死狀恐怖魄衅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塘辅,我是刑警寧澤晃虫,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扣墩,受9級(jí)特大地震影響哲银,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沮榜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一盘榨、第九天 我趴在偏房一處隱蔽的房頂上張望喻粹。 院中可真熱鬧蟆融,春花似錦、人聲如沸守呜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽查乒。三九已至弥喉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玛迄,已是汗流浹背由境。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虏杰。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓讥蟆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纺阔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘸彤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容