1. 程式人生 > >SciPyCon 2018 sklearn 教程(上)

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.todensetoarray返回一個 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_indexy_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