機器學習快人一步:初探scikit-learn

開干.第一步

導讀:機器學習是一門復雜的交叉學科。針對普通程序員來講,里面有太多的數學概念,我嘗試機器學習斷斷續續約半年,公式和概念這兩只攔路虎多次擋道,再加上自己數學太渣,毫無懸念的多次敗下陣來。
最近,我買了幾門機器學習的視頻課程,硬著頭皮從概率、微積分、統計分析、矩陣、凸優化開始看起,逐漸有了點兒感覺,雖有很多還是看不懂,但不至于看睡著。然后結合趙志勇的《Python機器學習算法》這本書,漸漸的也能上手了。如果你不是算法工程師,那就選擇看視頻了解概念,在寫代碼中使用成熟的機器學習算法包。
本文翻譯自scikit-learn v0.19.0的tutorial,希望能讓不熟悉機器學習的同學在代碼層面上有個直觀的了解。
雖然scikit-learn幫我們封裝了很多算法,可以黑盒調用,但是作為一個程序員,一定要清楚,會調包和只會調包是有很大的區別。

scikit-learn是Python(>= 2.7 or >= 3.3)中的機器學習包(庫),其構建在NumPy(>= 1.8.2), SciPy(>= 0.13.3)和 matplotlib之上。采用BSD授權,可放心的用在商業應用中。

  • 進行數據挖掘和數據分析的簡單而高效的工具
  • 任何人都可使用,可在多種場景/應用上下文中重復使用
  • 基于NumPy,SciPy和matplotlib構建
  • 開放源代碼,可用于商業用途-采用BSD協議

使用pip install -U scikit-learn命令在線安裝scikit-learn包。

安裝scikit-learn
安裝scikit-learn

安裝完畢后,執行以下示例,驗證安裝是否正確。

from sklearn import datasets
import matplotlib.pyplot as plt
#加載數字數據集
digits = datasets.load_digits()
#展示第一個數字
plt.figure(1, figsize=(3, 3))
plt.imshow(digits.images[-1], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
驗證安裝
驗證安裝

在Windows 10上安裝完成后,sklearn版本顯示為0.0,原因未知,但是能正常使用。

本教程使用版本:v0.19.0

原文:scikit-learn tutorial
本文參考了Tacey Wong在cnblogs上的譯文,針對最新版本做了調整,文中代碼全部經過驗證。

該章節,我們將介紹貫穿scikit-learn使用中的“ 機器學習(Machine Learning) ”這個詞匯,并給出一些簡單的學習示例。

0. 前言

機器學習的幾個關鍵主題有:分類、回歸、聚類、降維和預處理。

0.1 分類

分類算法,在實際工作中有很多中應用場景,如

  • 客戶標簽:描述客戶的風險等級程度,高、中或低
  • 欺詐評級:描述一筆交易的欺詐可能性,高、中或低

目的:識別一個對象屬于那一種類別
應用:垃圾郵件檢測,圖像識別
算法:SVM(支持向量機),KNN(K近鄰),隨機森林

分類
分類
不同的分類結果
不同的分類結果

0.2 回歸

目的:預測與某個對象相關聯的連續值屬性
應用:藥物反應,股票價格
算法:線性回歸,SVR(支持向量回歸),ridge regression(嶺回歸),LASSO回歸

線性回歸
線性回歸
LASSO回歸
LASSO回歸

0.3 聚類

目的:將相似的對象自動聚集到不同的集合中
應用:顧客細分,分組試驗結果
算法:K-Means,譜聚類,mean-shift中值移動

聚類
聚類

0.4 降維

降維打擊:請給我一片二向箔,清理用。

目的:降低隨機變量的數目
應用:可視化,提高效率
算法:PCA(主成分分析),特征選取,非負矩陣分解

降維
降維

0.5 預處理

目的:特征提取和正則化
應用:轉換數據以便機器學習算法使用
模塊:預處理,特征提取

1. 機器學習:問題設定

通常,一個學習問題是通過分析一些數據樣本來嘗試預測未知數據的屬性。如果每一個樣本不僅僅是一個單獨的數字,比如一個多維的實例(multivariate data),也就是說有著多個屬性特征。

我們可以把學習問題分成如下的幾個大類:

  • 有監督學習,數據帶有我們要預測的屬性。這種問題主要有如下幾種:

    • 分類: 樣例屬于兩類或多類,我們想要從已經帶有標簽(分類結果,如好/壞)的數據中學習以預測未帶標簽的數據。識別手寫數字就是一個分類問題,這個問題的主要目標就是把每一個輸出指派到一個有限的類別中的一類。另一種思路去思考分類問題,其實分類問題是有監督學習中的離散形式問題。每一個都有一個有限的分類。對于樣例提供的多個標簽,我們要做的就是把未知類別的數據劃分到其中的一種。
    • 回歸:如果預期的輸出包含連續的變量,那么這樣的任務叫做回歸。根據三文魚的年齡和重量預測其長度就是一個回歸的例子。
  • 無監督學習,針對那些訓練數據只包含輸入向量x但是不帶有目標值的場景,機器學習的目標就是根據數據發現樣本中相似的群組--聚類,或者是在輸入空間中判定數據的分布--密度估計,或者把數據從高維空間轉換到低維空間以用于可視化。

訓練集和測試集:機器學習是學習一些數據集的特征屬性并將其應用于新的數據集。這就是為什么在機器學習過程中用來評估算法時,一般會把手頭的數據分成兩部分。一部分我們稱之為訓練集,用以學習數據的特征屬性。另外一部分我們稱之為測試集,用以檢驗學習到的特征屬性。

2. 加載樣本數據集

scikit-learn本身帶有一些標準數據集。比如用來分類的iris(鳶尾花)數據集、digits(手寫數字圖像)數據集;用來回歸的boston house price(波士頓房屋價格) 數據集。

接下來,我們我們從shell開啟一個Python解釋器并加載iris和digits兩個數據集。本教程中的符號約定:$表示操作系統(Linux)提示符,>>>表示Python解釋器提示符。

$ python
>>> from sklearn import datasets #從sklearn包中加載數據集模塊
>>> iris = datasets.load_iris() #加載鳶尾花數據集
>>> digits = datasets.load_digits() #加載數字圖像數據集

一個數據集是一個包含數據所有元數據的類字典對象。這個數據存儲(X)在 '.data'成員變量中,是一個n_samplesn_features的數組,行表示樣例,列表示特征。在有監督學習問題中,一個或多個響應變量(Y)存儲在‘.target’成員變量中。不同數據集的更多細節可以在sklearn文檔的專屬章節中找到。

例如,對于digits數據集,digits.data可以訪問用來對數字進行分類的特征:

>>> print(digits.data)  
[[  0.   0.   5. ...,   0.   0.   0.]
 [  0.   0.   0. ...,  10.   0.   0.]
 [  0.   0.   0. ...,  16.   9.   0.]
 ...,
 [  0.   0.   1. ...,   6.   0.   0.]
 [  0.   0.   2. ...,  12.   0.   0.]
 [  0.   0.  10. ...,  12.   1.   0.]]

digits.target 就是數字數據集中各樣例對應的真實數字值。也就是我們的程序要學習的。

>>> digits.target
array([0, 1, 2, ..., 8, 9, 8])
探索digits數據集
探索digits數據集

數據數組的形狀
盡管原始數據也許有不同的形狀,但實際使用的數據通常是一個二維數組(n個樣例,n個特征)。對于digits數據集,每一個原始的樣例是一張(8 x 8)的圖片,使用以下的數組進行存取:

digits.images[0]
array([[  0.,   0.,   5.,  13.,   9.,   1.,   0.,   0.],
      [  0.,   0.,  13.,  15.,  10.,  15.,   5.,   0.],
      [  0.,   3.,  15.,   2.,   0.,  11.,   8.,   0.],
      [  0.,   4.,  12.,   0.,   0.,   8.,   8.,   0.],
      [  0.,   5.,   8.,   0.,   0.,   9.,   8.,   0.],
      [  0.,   4.,  11.,   0.,   1.,  12.,   7.,   0.],
      [  0.,   2.,  14.,   5.,  10.,  12.,   0.,   0.],
      [  0.,   0.,   6.,  13.,  10.,   0.,   0.,   0.]])

3. 學習和預測

對于數字數據集(digits dataset),任務是預測一張圖片中的數字是什么。數字數據集提供了0-9每一個數字的可能樣例,在此之上,我們選擇一個算法(estimator)對其進行擬合分類。

在scikit-learn中,用以分類的擬合(評估)函數是一個Python對象,具體有fit(X,y)和predic(T)兩種成員方法。

其中一個擬合(評估)樣例是sklearn.svmSVC類,它實現了支持向量分類(SVC)。一個擬合(評估)函數的構造函數需要模型的參數,但是時間問題,我們將會把這個擬合(評估)函數作為一個黑箱:

>>> from sklearn import svm
>>> clf = svm.SVC(gamma=0.001, C=100.)

選擇模型參數
在這個例子中,我們手動設定γ值。通常也可以使用網格搜索(grid search)交叉驗證(cross validation)等工具,自動找到較合適的參數值。

我們調用擬合(估測)實例clf作為我們的分類器。它現在必須要擬合模型,也就是說,它必須要學習模型。這可以通過把我們的訓練集傳遞給fit方法。作為訓練集,我們使用除最后一組的所有圖像。我們可以通過Python的分片語法[:-1]來選取訓練集,這個操作將產生一個新數組,這個數組包含digits.data中除最后一組數據的所有實例。

>>> clf.fit(digits.data[:-1], digits.target[:-1])  
SVC(C=100.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma=0.001, kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

現在你就可以預測新的數值了。我們可以讓這個分類器預測沒有作為訓練數據使用的最后一張圖像是什么數字。

>>> clf.predict(digits.data[-1:])
array([8])

相應的圖片如下圖:

預測的圖片
預測的圖片

正如你所看到的,這是一個很有挑戰的任務:這張圖片的分辨率很低。你同意分類器給出的答案嗎?

這個分類問題的完整示例在這里:識別手寫數字,你可以學習并運行它。

4. 模型持久化

可以使用Python的自帶模塊——pickle來保存scikit中的模型:

>>> from sklearn import svm
>>> from sklearn import datasets
>>> clf = svm.SVC()
>>> iris = datasets.load_iris()
>>> X, y = iris.data, iris.target
>>> clf.fit(X, y)  
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

>>> import pickle
>>> s = pickle.dumps(clf)
>>> clf2 = pickle.loads(s)
>>> clf2.predict(X[0:1])
array([0])
>>> y[0]
0

對于scikit,也許使用joblib的pickle替代--(joblib.dump&joblib.load)更有趣。因為它在處理大數據時更高效。但是遺憾的是它只能把數據持久化到硬盤而不內存。

>>> from sklearn.externals import joblib
>>> joblib.dump(clf, 'filename.pkl') 

之后,你就可以重新加載這個持久化后的模型(也能在另一個Python進程中使用),如下:

>>> clf = joblib.load('filename.pkl') 

注意:
joblib.dump和 joblib.load函數除了可以接受文件名參數外,也可接受文件類的對象。更多有關Joblib持久化數據的信息,請參考這里

可以返回一個文件名的列表,每一個numpy數組元素包含一個clf在文件系統上的名字,在用joblib.load加載的時候所有的文件需要在相同的文件夾下

注意:pickle有一些安全性和可維護性方面的問題。更多信息,請參考Model persistent ,以獲得在scikit-learn中模型持久化的更細節的信息。

5. 慣例約定

scikit-learn的各種擬合(評估)函數遵循一些確定的規則以使得他們的用法能夠更加統一。

5.1 類型轉換

除非明確指定,輸入將被轉換為float64

>>> import numpy as np
>>> from sklearn import random_projection

>>> rng = np.random.RandomState(0)
>>> X = rng.rand(10, 2000)
>>> X = np.array(X, dtype='float32')
>>> X.dtype
dtype('float32')

>>> transformer = random_projection.GaussianRandomProjection()
>>> X_new = transformer.fit_transform(X)
>>> X_new.dtype
dtype('float64')

在這個例子中,X是float32,被fit_transform(X)轉換成float64。

回歸的結果被轉換成float64,分類結果維持不變:

>>> from sklearn import datasets
>>> from sklearn.svm import SVC
>>> iris = datasets.load_iris()
>>> clf = SVC()
>>> clf.fit(iris.data, iris.target)  
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

>>> list(clf.predict(iris.data[:3]))
[0, 0, 0]

>>> clf.fit(iris.data, iris.target_names[iris.target])  
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

>>> list(clf.predict(iris.data[:3]))  
['setosa', 'setosa', 'setosa']

這里的第一個predict()返回一個整數數組,是因為iris.target(一個整數數組)被用于擬合。第二個predict()返回一個字符串數組,因為iris.target_names被用于擬合。

5.2 重擬合和更新參數

一個擬合(評估)函數的混合參數(超參數)能夠在通過sklearn.pipeline.Pipeline.set_params方法構造之后被更新。多次調用fit()能夠覆寫之前fit()學習的內容:

>>> import numpy as np
>>> from sklearn.svm import SVC

>>> rng = np.random.RandomState(0)
>>> X = rng.rand(100, 10)
>>> y = rng.binomial(1, 0.5, 100)
>>> X_test = rng.rand(5, 10)

>>> clf = SVC()
>>> clf.set_params(kernel='linear').fit(X, y)  
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='linear',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)
>>> clf.predict(X_test)
array([1, 0, 1, 1, 0])

>>> clf.set_params(kernel='rbf').fit(X, y)  
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)
>>> clf.predict(X_test)
array([0, 0, 0, 1, 0])

這里的擬合函數用SVC()構造之后,默認內核rbf被設置成'linear'擬合后做了第一次預測,后來又改回rbf去重擬合做第二次的預測。

5.3 Multiclass與multilabel擬合

當我們使用multiclass分類器時,學習和預測任務的執行就取決于擬合的目標數據格式:

>>> from sklearn.svm import SVC
>>> from sklearn.multiclass import OneVsRestClassifier
>>> from sklearn.preprocessing import LabelBinarizer

>>> X = [[1, 2], [2, 4], [4, 5], [3, 2], [3, 1]]
>>> y = [0, 0, 1, 1, 2]

>>> classif = OneVsRestClassifier(estimator=SVC(random_state=0))
>>> classif.fit(X, y).predict(X)
array([0, 0, 1, 1, 2])

在上述情況下,分類器在一個適合multiclass標簽的一維數組上進行擬合,
因此predict()方法提供multiclass相應的分類預測。

它還可以擬合二維二元標簽指標(binary label indicators)數組:

>>> y = LabelBinarizer().fit_transform(y)
>>> classif.fit(X, y).predict(X)
array([[1, 0, 0],
       [1, 0, 0],
       [0, 1, 0],
       [0, 0, 0],
       [0, 0, 0]])

這里,分類器使用LabelBinarizer在二元標簽(binary label,表示為y)上擬合。所以相應的預測也返回multilabel二維數組。

注意,第4和5個元素返回的都是0,表明它們與三個標簽中的任何一個都不匹配。用多標簽輸出時,對一個元素,有可能被打上多個標簽:

>>> from sklearn.preprocessing import MultiLabelBinarizer
>>> y = [[0, 1], [0, 2], [1, 3], [0, 2, 3], [2, 4]]
>>> y = MultiLabelBinarizer().fit_transform(y)
>>> classif.fit(X, y).predict(X)
array([[1, 1, 0, 0, 0],
       [1, 0, 1, 0, 0],
       [0, 1, 0, 1, 0],
       [1, 0, 1, 1, 0],
       [0, 0, 1, 0, 1]])

這種情況下,分類器擬合后,為每個元素都打上了多個標簽。MultiLabelBinarizer被用來二分擬合多標簽的二維數組。結果就是predict() 方法為返回的二維數組中的每個元素打上了多個標簽。

6. 示例

一個展示怎樣用scikit-learn識別手寫數字的樣例。

6.1 繪制數字

from sklearn import datasets
import matplotlib.pyplot as plt
#加載數字數據集
digits = datasets.load_digits()
#展示第一個數字
plt.figure(1, figsize=(3, 3))
plt.imshow(digits.images[-1], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()

6.2 識別手寫數字

以下是sklearn官方示例中的代碼,基于自帶的數據,使用支持向量分類器(svm.SVC(gamma=0.001))學習后進行預測。

# coding:UTF-8
print(__doc__)

# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# License: BSD 3 clause

# Standard scientific Python imports
import matplotlib.pyplot as plt

# Import datasets, classifiers and performance metrics
from sklearn import datasets, svm, metrics

# The digits dataset
digits = datasets.load_digits()

# The data that we are interested in is made of 8x8 images of digits, let's
# have a look at the first 4 images, stored in the `images` attribute of the
# dataset.  If we were working from image files, we could load them using
# matplotlib.pyplot.imread.  Note that each image must have the same size. For these
# images, we know which digit they represent: it is given in the 'target' of
# the dataset.
images_and_labels = list(zip(digits.images, digits.target))
for index, (image, label) in enumerate(images_and_labels[:4]):
    plt.subplot(2, 4, index + 1)
    plt.axis('off')
    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Training: %i' % label)

# To apply a classifier on this data, we need to flatten the image, to
# turn the data in a (samples, feature) matrix:
n_samples = len(digits.images)
data = digits.images.reshape((n_samples, -1))

# Create a classifier: a support vector classifier
classifier = svm.SVC(gamma=0.001)

# We learn the digits on the first half of the digits
classifier.fit(data[:n_samples // 2], digits.target[:n_samples // 2])

# Now predict the value of the digit on the second half:
expected = digits.target[n_samples // 2:]
predicted = classifier.predict(data[n_samples // 2:])

print("Classification report for classifier %s:\n%s\n"
      % (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))

images_and_predictions = list(zip(digits.images[n_samples // 2:], predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:4]):
    plt.subplot(2, 4, index + 5)
    plt.axis('off')
    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Prediction: %i' % prediction)

plt.show()

以上代碼保存到python源文件中,在PyCharm中執行,結果如下:

運行結果
運行結果
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容