SciPyCon 2018 sklearn 教程(上)
一、Python 機器學習簡介
什麼是機器學習?
機器學習是自動從資料中提取知識的過程,通常是為了預測新的,看不見的資料。一個典型的例子是垃圾郵件過濾器,使用者將傳入的郵件標記為垃圾郵件或非垃圾郵件。然後,機器學習演算法從資料“學習”預測模型,資料區分垃圾郵件和普通電子郵件。該模型可以預測新電子郵件是否是垃圾郵件。
機器學習的核心是根據資料來自動化決策的概念,無需使用者指定如何做出此決策的明確規則。
對於電子郵件,使用者不提供垃圾郵件的單詞或特徵列表。相反,使用者提供標記為垃圾郵件和非垃圾郵件的示例。
第二個核心概念是泛化。機器學習模型的目標是預測新的,以前沒見過的資料。在實際應用中,將已標記的電子郵件標記為垃圾郵件,我們不感興趣。相反,我們希望通過自動分類新的傳入郵件來使使用者更輕鬆。
資料通常作為數字的二維陣列(或矩陣)展示給演算法。 我們想要學習或做出決策的每個資料點(也稱為樣本或訓練例項)表示為數字列表,即所謂的特徵向量,其包含的特徵表示這個點的屬性。
稍後,我們將使用一個名為鳶尾花(Iris)的流行資料集 - 在許多其他資料集中。鳶尾花是機器學習領域的經典基準資料集,包含來自 3 種不同物種的 150 種鳶尾花的測量值:Iris-Setosa(山鳶尾),Iris-Versicolor(雜色鳶尾)和 Iris-Virginica(弗吉尼亞鳶尾)。
物種 | 影象 |
---|---|
山鳶尾 | |
雜色鳶尾 | |
弗吉尼亞鳶尾 |
我們將每個花樣本表示為資料陣列中的一行,列(特徵)表示以釐米為單位的花測量值。 例如,我們可以用以下格式表示這個鳶尾花資料集,包括 150 個樣本和 4 個特徵,一個150×4
$\mathbf{X} = \begin{bmatrix} x_{1}^{(1)} & x_{2}^{(1)} & x_{3}^{(1)} & \dots & x_{4}^{(1)} \ x_{1}^{(2)} & x_{2}^{(2)} & x_{3}^{(2)} & \dots & x_{4}^{(2)} \ \vdots & \vdots & \vdots & \ddots & \vdots \ x_{1}^{(150)} & x_{2}^{(150)} & x_{3}^{(150)} & \dots & x_{4}^{(150)} \end{bmatrix} $
(上標表示第i
行,下標分別表示第j
個特徵。
我們今天將討論兩種機器學習:監督學習和無監督學習。
監督學習:分類和迴歸
在監督學習中,我們有一個數據集,由輸入特徵和所需輸出組成的,例如垃圾郵件/非垃圾郵件示例。 任務是構建一個模型(或程式),它能夠在給定特徵集的情況下預測未見過的物件的所需輸出。
一些更復雜的例子是:
- 通過望遠鏡給定物體的多色影象,確定該物體是星星,類星體還是星系。
- 給定一個人的照片,識別照片中的人物。
- 給定一個人觀看的電影列表和他們對電影的個人評價,推薦他們想要的電影列表。
- 給定一個人的年齡,教育程度和職位,推斷他們的薪水
這些任務的共同之處在於,存在與該物件相關聯的一個或多個未知量,其需要從其他觀察量確定。
監督學習進一步細分為兩類,分類和迴歸:
在分類中,標籤是離散的,例如“垃圾郵件”或“無垃圾郵件”。換句話說,它提供了類別之間的明確區分。此外,重要的是注意類標籤是標稱的,而不是序數變數。標稱和序數變數都是類別變數的子類別。序數變數意味著順序,例如,T 恤尺寸XL> L> M> S
。相反,標稱變數並不意味著順序,例如,我們(通常)不能假設“橙色>藍色>綠色”。
在迴歸中,標籤是連續的,即浮點輸出。例如,在天文學中,確定物體是星星,星系還是類星體的任務是分類問題:標籤來自三個不同的類別。另一方面,我們可能希望根據這些觀察來估計物體的年齡:這將是一個迴歸問題,因為標籤(年齡)是一個連續的數量。
在監督學習中,在提供期望結果的訓練集與需要根據它推斷期望結果的測試集之間,總是存在區別。模型的學習使預測模型擬合訓練集,我們使用測試集來評估其泛化表現。
無監督學習
在無監督學習中,沒有與資料相關的期望輸出。相反,我們有興趣從給定的資料中提取某種形式的知識或模型。從某種意義上說,你可以將無監督學習視為從資料本身發現標籤的一種手段。無監督學習通常難以理解和評估。
無監督學習包括降維,聚類和密度估計之類的任務。例如,在上面討論的鳶尾花資料中,我們可以使用無監督方法來確定顯示資料結構的最佳測量值組合。我們將在下面看到,這種資料投影可用於在二維中視覺化四維資料集。更多涉及無監督學習的問題是:
- 給定對遙遠星系的詳細觀察,確定哪些特徵或特徵組合總結了最佳資訊。
- 給定兩個聲源的混合(例如,一個人的談話和一些音樂),將兩者分開(這稱為盲源分離問題)。
- 給定視訊,隔離移動物體並相對於已看到的其他移動物體進行分類。
- 給定大量新聞文章,在這些文章中找到重複出現的主題。
- 給定一組影象,將相似的影象聚集在一起(例如,在視覺化集合時對它們進行分組)
有時兩者甚至可以合併:例如無監督學習可用於在異構資料中找到有用的特徵,然後可以在監督框架內使用這些特徵。
(簡化的)機器學習分類法
二、Python 中的科學計算工具
Jupyter Notebooks
你可以按[shift] + [Enter]
或按選單中的“播放”按鈕來執行單元格。
在function(
後面按[shift] + [tab]
,可以獲得函式或物件的幫助。
你還可以通過執行function?
獲得幫助。
NumPy 陣列
操作numpy
陣列是 Python 機器學習(或者,實際上是任何型別的科學計算)的重要部分。 對大多數人來說,這可能是一個簡短的回顧。 無論如何,讓我們快速瀏覽一些最重要的功能。
import numpy as np
# 設定隨機種子來獲得可重複性
rnd = np.random.RandomState(seed=123)
# 生成隨機陣列
X = rnd.uniform(low=0.0, high=1.0, size=(3, 5)) # a 3 x 5 array
print(X)
(請注意,NumPy 陣列使用從 0 開始的索引,就像 Python 中的其他資料結構一樣。)
# 元素訪問
# 獲取單個元素
# (這裡是第一行第一列的元素)
print(X[0, 0])
# 獲取一行
# (這裡是第二行)
print(X[1])
# 獲取一列
# (這裡是第二列)
print(X[:, 1])
# 陣列轉置
print(X.T)
$\begin{bmatrix} 1 & 5 \ 2 & 6 \ 3 & 7 \ 4 & 8 \end{bmatrix} $
# 在指定的時間間隔內建立均勻間隔的數字的行向量。
y = np.linspace(0, 12, 5)
print(y)
# 將行向量轉換為列向量
print(y[:, np.newaxis])
# 獲得形狀或改變陣列形狀
# 生成隨機陣列
rnd = np.random.RandomState(seed=123)
X = rnd.uniform(low=0.0, high=1.0, size=(3, 5)) # a 3 x 5 array
print(X.shape)
# 將 X 大小變為 (3, 5)
X_reshaped = X.reshape(5, 3)
print(X_reshaped)
# 使用整數陣列的索引(花式索引)
indices = np.array([3, 1, 0])
print(indices)
X[:, indices]
還有很多東西要知道,但是這些操作對於我們在本教程中將要做的事情至關重要。
SciPy 稀疏陣列
我們不會在本教程中大量使用它們,但稀疏矩陣在某些情況下非常好用。 在一些機器學習任務中,尤其是與文字分析相關的任務,資料可能大多為零。 儲存所有這些零是非常低效的,並且以僅包含“非零”值的方式表示可以更有效。 我們可以建立和操作稀疏矩陣,如下所示:
# 建立一個包含大量零的隨機陣列
rnd = np.random.RandomState(seed=123)
X = rnd.uniform(low=0.0, high=1.0, size=(10, 5))
print(X)
# 將大多數元素設定為零
X[X < 0.7] = 0
print(X)
from scipy import sparse
# 將 X 轉換為 CSR(壓縮稀疏行)矩陣
X_csr = sparse.csr_matrix(X)
print(X_csr)
# 將稀疏矩陣轉換為密集陣列
print(X_csr.toarray())
(你可能偶然發現了一種將稀疏表示轉換為密集表示的替代方法:numpy.todense
;toarray
返回一個 NumPy 陣列,而todense
返回一個 NumPy 矩陣。在本教程中,我們將使用 NumPy 陣列,而不是矩陣;scikit-learn 不支援後者。)
CSR 表示對於計算非常有效,但它不適合新增元素。 為此,LIL(List-In-List)表示更好:
# 建立一個空的 LIL 矩陣並新增一些專案
X_lil = sparse.lil_matrix((5, 5))
for i, j in np.random.randint(0, 5, (15, 2)):
X_lil[i, j] = i + j
print(X_lil)
print(type(X_lil))
X_dense = X_lil.toarray()
print(X_dense)
print(type(X_dense))
通常,一旦建立了 LIL 矩陣,將其轉換為 CSR 格式很有用(許多 scikit-learn 演算法需要 CSR 或 CSC 格式)
X_csr = X_lil.tocsr()
print(X_csr)
print(type(X_csr))
可用於各種問題的可用稀疏格式包括:
CSR
(壓縮稀疏行)CSC
(壓縮稀疏列)BSR
(塊稀疏行)COO
(座標)DIA
(對角線)DOK
(鍵的字典)LIL
(列表中的列表)
scipy.sparse
子模組還有很多稀疏矩陣的函式,包括線性代數,稀疏求解器,圖演算法等等。
Matplotlib
機器學習的另一個重要部分是資料視覺化。 Python 中最常用的工具是matplotlib
。 這是一個非常靈活的包,我們將在這裡介紹一些基礎知識。
由於我們使用的是 Jupyter 筆記本,讓我們使用 IPython 方便的內建“魔術函式”,即“matoplotlib
內聯”模式,它將直接在筆記本內部繪製圖形。
%matplotlib inline
import matplotlib.pyplot as plt
# 繪製直線
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x));
# 散點圖
x = np.random.normal(size=500)
y = np.random.normal(size=500)
plt.scatter(x, y);
# 使用 imshow 展示繪圖
# - note that origin is at the top-left by default!
x = np.linspace(1, 12, 100)
y = x[:, np.newaxis]
im = y * np.sin(x) * np.cos(y)
print(im.shape)
plt.imshow(im);
# 輪廓圖
# - 請注意,此處的原點預設位於左下角!
plt.contour(im);
# 3D 繪圖
from mpl_toolkits.mplot3d import Axes3D
ax = plt.axes(projection='3d')
xgrid, ygrid = np.meshgrid(x, y.ravel())
ax.plot_surface(xgrid, ygrid, im, cmap=plt.cm.viridis, cstride=2, rstride=2, linewidth=0);
有許多可用的繪圖型別。 探索它們的一個實用方法是檢視matplotlib庫。
你可以在筆記本中輕鬆測試這些示例:只需複製每頁上的原始碼連結,然後使用%load magic
將其放入筆記本中。 例如:
# %load http://matplotlib.org/mpl_examples/pylab_examples/ellipse_collection.py
三、資料表示和視覺化
機器學習關於將模型擬合到資料;出於這個原因,我們首先討論如何表示資料以便計算機理解。 除此之外,我們將基於上一節中的matplotlib
示例構建,並展示如何視覺化資料的一些示例。
sklearn 中的資料
scikit-learn 中的資料(極少數例外)被假定儲存為形狀為[n_samples, n_features]
的二維陣列。許多演算法也接受形狀相同的scipy.sparse
矩陣。
n_samples
:樣本數量:每個樣本是要處理(例如分類)的專案。樣本可以是文件,圖片,聲音,視訊,天文物件,資料庫中的行或 CSV 檔案,或者你可以使用的一組固定數量的特徵描述的任何內容。n_features
:特徵或不同形狀的數量,可用於以定量方式描述每個專案。特徵通常是實值,但在某些情況下可以是布林值或離散值。
必須事先固定特徵的數量。然而,它可以是非常高的維度(例如數百萬個特徵),對於給定的樣本,它們中的大多數是“零”。這是scipy.sparse
矩陣可能有用的情況,因為它們比 NumPy 陣列更具記憶體效率。
我們從上一節(或 Jupyter 筆記本)中回顧,我們將樣本(資料點或例項)表示為資料陣列中的行,並將相應的特徵(“維度”)儲存為列。
簡單示例:鳶尾花資料集
作為簡單資料集的一個例子,我們將看一下 scikit-learn 儲存的鳶尾花資料。 資料包括三種不同鳶尾花的測量值。 在這個特定的資料集中有三種不同的鳶尾花,如下圖所示:
物種 | 影象 |
---|---|
山鳶尾 | |
雜色鳶尾 | |
弗吉尼亞鳶尾 |
簡單問題:
讓我們假設我們有興趣對新觀測值進行分類; 我們想分別預測未知的花是 Iris-Setosa,Iris-Versicolor 還是 Iris-Virginica。 根據我們在上一節中討論的內容,我們將如何構建這樣的資料集?
記住:我們需要一個大小為[n_samples x n_features]
的二維陣列。
n_samples
指代什麼?n_features
可能指代什麼?
請記住,每個樣本必須有固定數量的特徵,並且對於每個樣本,特徵編號j
必須是同一種數量。
在 sklearn 中載入鳶尾花資料集
對於將來使用機器學習演算法實驗,我們建議你收藏 UCI 機器學習倉庫,該倉庫託管許多常用的資料集,這些資料集對於機器學習演算法的基準測試非常有用 - 這是機器學習實踐者和研究人員非常流行的資源。 方便的是,其中一些資料集已經包含在 scikit-learn 中,因此我們可以跳過下載,讀取,解析和清理這些文字/ CSV 檔案的繁瑣部分。你可以在這裡找到 scikit-learn 中可用資料集的列表。
如,scikit-learn 擁有這些鳶尾花物種的非常簡單的資料集。 資料包括以下內容:
鳶尾花資料集中的特徵:
- 萼片長度,釐米
- 萼片寬度,釐米
- 花瓣長度,釐米
- 花瓣寬度,釐米
要預測的目標類別:
- 山鳶尾
- 雜色鳶尾
- 弗吉尼亞鳶尾
(圖片來源:“Petal-sepal”。通過 Wikimedia Commons 在 CC BY-SA 3.0 下獲得許可)
scikit-learn 自帶了鳶尾花 CSV 檔案的副本以及輔助函式,用於將其載入到numpy
陣列中:
from sklearn.datasets import load_iris
iris = load_iris()
生成的資料集是一個Bunch
物件:你可以使用方法keys()
檢視可用的內容:
iris.keys()
每個花樣本的特徵都儲存在資料集的data
屬性中:
n_samples, n_features = iris.data.shape
print('Number of samples:', n_samples)
print('Number of features:', n_features)
# 第一個樣本(第一朵花)的萼片長度,萼片寬度,花瓣長度和花瓣寬度
print(iris.data[0])
每個樣本的類別資訊儲存在資料集的target
屬性中:
print(iris.data.shape)
print(iris.target.shape)
print(iris.target)
import numpy as np
np.bincount(iris.target)
使用 NumPy 的bincount
函式(上圖),我們可以看到類別在這個資料集中均勻分佈 - 每個物種有 50 朵花,其中:
- 類 0:山鳶尾
- 類 1:雜色鳶尾
- 類 2:弗吉尼亞鳶尾
這些類名儲存在最後一個屬性中,即target_names
:
print(iris.target_names)
這個資料是四維的,但我們可以使用簡單的直方圖或散點圖一次視覺化一個或兩個維度。 再次,我們將從啟用matplotlib
內聯模式開始:
%matplotlib inline
import matplotlib.pyplot as plt
x_index = 3
for label in range(len(iris.target_names)):
plt.hist(iris.data[iris.target==label, x_index],
label=iris.target_names[label],
alpha=0.5)
plt.xlabel(iris.feature_names[x_index])
plt.legend(loc='upper right')
plt.show()
x_index = 3
y_index = 0
for label in range(len(iris.target_names)):
plt.scatter(iris.data[iris.target==label, x_index],
iris.data[iris.target==label, y_index],
label=iris.target_names[label])
plt.xlabel(iris.feature_names[x_index])
plt.ylabel(iris.feature_names[y_index])
plt.legend(loc='upper left')
plt.show()
練習
- 在上面的指令碼中,更改
x_index
和y_index
,找到兩個引數的組合,最大限度地將這三個類分開。- 本練習是降維的預習,我們稍後會看到。
旁註:散點圖矩陣
分析人員使用的常用工具稱為散點圖矩陣,而不是一次檢視一個繪圖。
散點圖矩陣顯示資料集中所有特徵之間的散點圖,以及顯示每個特徵分佈的直方圖。
import pandas as pd
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
pd.plotting.scatter_matrix(iris_df, c=iris.target, figsize=(8, 8));
其它可用的資料
- 打包資料:這些小資料集與 scikit-learn 安裝打包在一起,可以使用
sklearn.datasets.load_ *
中的工具下載 - 可下載資料:這些較大的資料集可供下載,scikit-learn 包含簡化此過程的工具。 這些工具可以在
sklearn.datasets.fetch_ *
中找到 - 生成的資料:有幾個資料集是基於隨機種子從模型生成的。 這些可以在
sklearn.datasets.make_ *
中找到
你可以使用 IPython 的製表符補全功能探索可用的資料集載入器,提取器和生成器。 從sklearn
匯入datasets
子模組後,鍵入:
datasets.load_<TAB>
或者:
datasets.fetch_<TAB>
或者:
datasets.make_<TAB>
來檢視可用函式列表。
from sklearn import datasets
請注意:許多這些資料集非常龐大,可能需要很長時間才能下載!
如果你在 IPython 筆記本中開始下載並且想要將其刪除,則可以使用 ipython 的“核心中斷”功能,該功能可在選單中使用或使用快捷鍵Ctrl-m i
。
你可以按Ctrl-m h
獲取所有 ipython 鍵盤快捷鍵的列表。
載入數字資料
現在我們來看看另一個數據集,我們必須更多考慮如何表示資料。 我們可以採用與上述類似的方式探索資料:
from sklearn.datasets import load_digits
digits = load_digits()
digits.keys()
n_samples, n_features = digits.data.shape
print((n_samples, n_features))
print(digits.data[0])
print(digits.target)
這裡的目標只是資料所代表的數字。 資料是長度為 64 的陣列…但這些資料意味著什麼?
實際上有個線索,我們有兩個版本的資料陣列:資料和影象。 我們來看看它們:
print(digits.data.shape)
print(digits.images.shape)
通過簡單的形狀改變,我們可以看到它們是相關的:
import numpy as np
print(np.all(digits.images.reshape((1797, 64)) == digits.data))
讓我們視覺化資料。 它比我們上面使用的簡單散點圖更復雜,但我們可以很快地完成它。
# 建立圖形
fig = plt.figure(figsize=(6, 6)) # figure size in inches
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
# 繪製數字:每個影象是 8x8 畫素
for i in range(64):
ax = fig.add_subplot(8, 8, i + 1, xticks=[], yticks=[])
ax.imshow(digits.images[i], cmap=plt.cm.binary, interpolation='nearest')
# 用目標值標記影象
ax.text(0, 7, str(digits.target[i]))
我們現在看到這些特徵的含義。 每個特徵是實數值,表示手寫數字的 8×8 影象中的畫素的暗度。
即使每個樣本具有固有的二維資料,資料矩陣也將該 2D 資料展平為單個向量,該向量可以包含在資料矩陣的一行中。
練習:處理人臉資料集
這裡,我們將花點時間親自探索資料集。 稍後我們將使用 Olivetti faces 資料集。 花點時間獲取資料(大約 1.4MB),並可視化人臉。 你可以複製用於視覺化上述數字的程式碼,併為此資料進行修改。
from sklearn.datasets import fetch_olivetti_faces
# 獲取人臉資料
# 使用上面的指令碼繪製人臉影象資料。
# 提示:plt.cm.bone 是用於這個資料的很好的顏色表
答案:
# %load solutions/03A_faces_plot.py
四、訓練和測試資料
為了評估我們的監督模型的泛化能力,我們可以將資料分成訓練和測試集:
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
考慮如何正常執行機器學習,訓練/測試分割的想法是有道理的。真實世界系統根據他們擁有的資料進行訓練,當其他資料進入時(來自客戶,感測器或其他來源),經過訓練的分類器必須預測全新的資料。我們可以在訓練期間使用訓練/測試分割來模擬 - 測試資料是“未來資料”的模擬,它將在生產期間進入系統。
特別是對於鳶尾花,其中的 150 個標籤是有序的,這意味著如果我們使用比例分割來分割資料,這將導致類分佈基本上改變。例如,如果我們執行常見的 2/3 訓練資料和 1/3 測試資料的分割,我們的訓練資料集將僅包含類別 0 和 1(Setosa 和 Versicolor),我們的測試集將僅包含類別標籤為 2 的樣本(Virginica)。
假設所有樣本彼此獨立(而不是時間序列資料),我們希望在分割資料集之前隨機打亂資料集。
現在我們需要將資料分成訓練和測試集。 幸運的是,這是機器學習中常見的模式,scikit-learn 具有預先構建的函式,可以將資料分成訓練和測試集。 在這裡,我們使用 50% 的資料來訓練,50% 來測試。 80% 和 20% 是另一種常見的分割,但沒有嚴格的規則。 最重要的是,要在訓練期間未見過的資料上,公平地評估您的系統!
from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y = train_test_split(X, y,
train_size=0.5,
test_size=0.5,
random_state=123)
print("Labels for training data:")
print(train_y)
print("Labels for test data:")
print(test_y)
提示:分層分割
特別是對於相對較小的資料集,最好分層分割。 分層意味著我們在測試和訓練集中保持資料集的原始類比例。 例如,在我們隨機拆分前面的程式碼示例中所示的資料集之後,我們的類比例(百分比)如下:
print('All:', np.bincount(y) / float(len(y)) * 100.0)
print('Training:', np.bincount(train_y) / float(len(train_y)) * 100.0)
print('Test:', np.bincount(test_y) / float(len(test_y)) * 100.0)
因此,為了分層分割,我們可以將label
陣列作為附加選項傳遞給train_test_split
函式:
train_X, test_X, train_y, test_y = train_test_split(X, y,
train_size=0.5,
test_size=0.5,
random_state=123,
stratify=y)
print('All:', np.bincount(y) / float(len(y)) * 100.0)
print('Training:', np.bincount(train_y) / float(len(train_y)) * 100.0)
print('Test:', np.bincount(test_y) / float(len(test_y)) * 100.0)
通過在訓練過程中看到的資料上評估我們的分類器效能,我們可能對模型的預測能力產生錯誤的信心。 在最壞的情況下,它可能只是記住訓練樣本,但完全沒有分類新的類似樣本 - 我們真的不想將這樣的系統投入生產!
不使用相同的資料集進行訓練和測試(這稱為“重取代評估”),為了估計訓練模型對新資料的效果,使用訓練/測試分割要好得多。
from sklearn.neighbors import KNeighborsClassifier
classifier = KNeighborsClassifier().fit(train_X, train_y)
pred_y = classifier.predict(test_X)
print("Fraction Correct [Accuracy]:")
print(np.sum(pred_y == test_y