1. 程式人生 > >機器學習演算法一覽,應用建議與解決思路 (實用!!!劃重點!!!)

機器學習演算法一覽,應用建議與解決思路 (實用!!!劃重點!!!)

1.引言

提起筆來寫這篇部落格,突然有點愧疚和尷尬。愧疚的是,工作雜事多,加之懶癌嚴重,導致這個系列一直沒有更新,向關注該系列的同學們道個歉。尷尬的是,按理說,機器學習介紹與演算法一覽應該放在最前面寫,詳細的應用建議應該在講完機器學習常用演算法之後寫,突然莫名奇妙在中間插播這麼一篇,好像有點打亂主線。  老話說『亡羊補牢,為時未晚』,前面開頭忘講的東西,咱在這塊兒補上。我們先帶著大家過一遍傳統機器學習演算法,基本思想和用途。把問題解決思路和方法應用建議提前到這裡的想法也很簡單,希望能提前給大家一些小建議,對於某些容易出錯的地方也先給大家打個預防針,這樣在理解後續相應機器學習演算法之後,使用起來也有一定的章法。

2.機器學習演算法簡述

按照不同的分類標準,可以把機器學習的演算法做不同的分類。

2.1 從機器學習問題角度分類

我們先從機器學習問題本身分類的角度來看,我們可以分成下列型別的演算法:

  • 監督學習演算法

機器學習中有一大部分的問題屬於『監督學習』的範疇,簡單口語化地說明,這類問題中,給定的訓練樣本中,每個樣本的輸入xx都對應一個確定的結果yy,我們需要訓練出一個模型(數學上看是一個x→yx→y的對映關係ff),在未知的樣本x′x′給定後,我們能對結果y′y′做出預測。

這裡的預測結果如果是離散值(很多時候是類別型別,比如郵件分類問題中的垃圾郵件/普通郵件,比如使用者會/不會購買某商品),那麼我們把它叫做分類問題(classification problem);如果預測結果是連續值

(比如房價,股票價格等等),那麼我們把它叫做迴歸問題(regression problem)。

有一系列的機器學習演算法是用以解決監督學習問題的,比如最經典的用於分類問題的樸素貝葉斯、邏輯迴歸、支援向量機等等;比如說用於迴歸問題的線性迴歸等等。

  • 無監督學習

有另外一類問題,給我們的樣本並沒有給出『標籤/標準答案』,就是一系列的樣本。而我們需要做的事情是,在一些樣本中抽取出通用的規則。這叫做『無監督學習』。包括關聯規則和聚類演算法在內的一系列機器學習演算法都屬於這個範疇。

  • 半監督學習

這類問題給出的訓練資料,有一部分有標籤,有一部分沒有標籤。我們想學習出資料組織結構的同時,也能做相應的預測。此類問題相對應的機器學習演算法有自訓練(Self-Training)、直推學習(Transductive Learning)、生成式模型(Generative Model)等。

總體說來,最常見是前兩類問題,而對應前兩類問題的一些機器學習演算法如下:

機器學習演算法

2.2 從演算法的功能角度分類

我們也可以從演算法的共性(比如功能,運作方式)角度對機器學習演算法分類。下面我們根據演算法的共性去對它們歸個類。不過需要注意的是,我們下面的歸類方法可能對分類和迴歸有比較強的傾向性,而這兩類問題也是最常遇到的。

2.2.1 迴歸演算法(Regression Algorithms)

迴歸演算法

迴歸演算法是一種通過最小化預測值與實際結果值之間的差距,而得到輸入特徵之間的最佳組合方式的一類演算法。對於連續值預測有線性迴歸等,而對於離散值/類別預測,我們也可以把邏輯迴歸等也視作迴歸演算法的一種,常見的迴歸演算法如下:

  • Ordinary Least Squares Regression (OLSR)
  • Linear Regression
  • Logistic Regression
  • Stepwise Regression
  • Locally Estimated Scatterplot Smoothing (LOESS)
  • Multivariate Adaptive Regression Splines (MARS)

2.2.2 基於例項的演算法(Instance-based Algorithms)

基於例項的演算法

這裡所謂的基於例項的演算法,我指的是我們最後建成的模型,對原始資料樣本例項依舊有很強的依賴性。這類演算法在做預測決策時,一般都是使用某類相似度準則,去比對待預測的樣本和原始樣本的相近度,再給出相應的預測結果。常見的基於例項的演算法有:

  • k-Nearest Neighbour (kNN)
  • Learning Vector Quantization (LVQ)
  • Self-Organizing Map (SOM)
  • Locally Weighted Learning (LWL)

2.2.3 決策樹類演算法(Decision Tree Algorithms)

決策樹類演算法

決策樹類演算法,會基於原始資料特徵,構建一顆包含很多決策路徑的樹。預測階段選擇路徑進行決策。常見的決策樹演算法包括:

  • Classification and Regression Tree (CART)
  • Iterative Dichotomiser 3 (ID3)
  • C4.5 and C5.0 (different versions of a powerful approach)
  • Chi-squared Automatic Interaction Detection (CHAID)
  • M5
  • Conditional Decision Trees

2.2.4 貝葉斯類演算法(Bayesian Algorithms)

貝葉斯類演算法

這裡說的貝葉斯類演算法,指的是在分類和迴歸問題中,隱含使用了貝葉斯原理的演算法。包括:

  • Naive Bayes
  • Gaussian Naive Bayes
  • Multinomial Naive Bayes
  • Averaged One-Dependence Estimators (AODE)
  • Bayesian Belief Network (BBN)
  • Bayesian Network (BN)

2.2.5 聚類演算法(Clustering Algorithms)

聚類演算法

聚類演算法做的事情是,把輸入樣本聚成圍繞一些中心的『資料團』,以發現數據分佈結構的一些規律。常用的聚類演算法包括:

  • k-Means
  • Hierarchical Clustering
  • Expectation Maximisation (EM)

2.2.6 關聯規則演算法(Association Rule Learning Algorithms)

關聯規則演算法

關聯規則演算法是這樣一類演算法:它試圖抽取出,最能解釋觀察到的訓練樣本之間關聯關係的規則,也就是獲取一個事件和其他事件之間依賴或關聯的知識,常見的關聯規則演算法有:

  • Apriori algorithm
  • Eclat algorithm

2.2.7 人工神經網路類演算法(Artificial Neural Network Algorithms)

人工神經網路類演算法

這是受人腦神經元工作方式啟發而構造的一類演算法。需要提到的一點是,我把『深度學習』單拎出來了,這裡說的人工神經網路偏向於更傳統的感知演算法,主要包括:

  • Perceptron
  • Back-Propagation
  • Radial Basis Function Network (RBFN)

2.2.8 深度學習(Deep Learning Algorithms)

深度學習

深度學習是近年來非常火的機器學習領域,相對於上面列的人工神經網路演算法,它通常情況下,有著更深的層次和更復雜的結構。有興趣的同學可以看看我們另一個系列機器學習與計算機視覺,最常見的深度學習演算法包括:

  • Deep Boltzmann Machine (DBM)
  • Deep Belief Networks (DBN)
  • Convolutional Neural Network (CNN)
  • Stacked Auto-Encoders

2.2.9 降維演算法(Dimensionality Reduction Algorithms)

降維演算法

從某種程度上說,降維演算法和聚類其實有點類似,因為它也在試圖發現原始訓練資料的固有結構,但是降維演算法在試圖,用更少的資訊(更低維的資訊)總結和描述出原始資訊的大部分內容。  有意思的是,降維演算法一般在資料的視覺化,或者是降低資料計算空間有很大的作用。它作為一種機器學習的演算法,很多時候用它先處理資料,再灌入別的機器學習演算法學習。主要的降維演算法包括:

  • Principal Component Analysis (PCA)
  • Principal Component Regression (PCR)
  • Partial Least Squares Regression (PLSR)
  • Sammon Mapping
  • Multidimensional Scaling (MDS)
  • Linear Discriminant Analysis (LDA)
  • Mixture Discriminant Analysis (MDA)
  • Quadratic Discriminant Analysis (QDA)
  • Flexible Discriminant Analysis (FDA)

2.2.10 模型融合演算法(Ensemble Algorithms)

模型融合演算法

嚴格意義上來說,這不算是一種機器學習演算法,而更像是一種優化手段/策略,它通常是結合多個簡單的弱機器學習演算法,去做更可靠的決策。拿分類問題舉個例,直觀的理解,就是單個分類器的分類是可能出錯,不可靠的,但是如果多個分類器投票,那可靠度就會高很多。常用的模型融合增強方法包括:

  • Random Forest
  • Boosting
  • Bootstrapped Aggregation (Bagging)
  • AdaBoost
  • Stacked Generalization (blending)
  • Gradient Boosting Machines (GBM)
  • Gradient Boosted Regression Trees (GBRT)

2.3 機器學習演算法使用圖譜

scikit-learn作為一個豐富的python機器學習庫,實現了絕大多數機器學習的演算法,有相當多的人在使用,於是我這裡很無恥地把machine learning cheat sheet for sklearn搬過來了,原文可以看這裡。哈哈,既然講機器學習,我們就用機器學習的語言來解釋一下,這是針對實際應用場景的各種條件限制,對scikit-learn裡完成的演算法構建的一顆決策樹,每一組條件都是對應一條路徑,能找到相對較為合適的一些解決方法,具體如下:

sklearn機器學習演算法使用圖譜

首先樣本量如果非常少的話,其實所有的機器學習演算法都沒有辦法從裡面『學到』通用的規則和模式,so多弄點資料是王道。然後根據問題是有/無監督學習和連續值/離散值預測,分成了分類聚類迴歸維度約減四個方法類,每個類里根據具體情況的不同,又有不同的處理方法。

3. 機器學習問題解決思路

上面帶著代價走馬觀花過了一遍機器學習的若干演算法,下面我們試著總結總結在拿到一個實際問題的時候,如果著手使用機器學習演算法去解決問題,其中的一些注意點以及核心思路。主要包括以下內容:

  • 拿到資料後怎麼了解資料(視覺化)
  • 選擇最貼切的機器學習演算法
  • 定位模型狀態(過/欠擬合)以及解決方法
  • 大量極的資料的特徵分析與視覺化
  • 各種損失函式(loss function)的優缺點及如何選擇

多說一句,這裡寫的這個小教程,主要是作為一個通用的建議和指導方案,你不一定要嚴格按照這個流程解決機器學習問題。

3.1 資料與視覺化

我們先使用scikit-learn的make_classification函式來生產一份分類資料,然後模擬一下拿到實際資料後我們需要做的事情。

#numpy科學計算工具箱
import numpy as np
#使用make_classification構造1000個樣本,每個樣本有20個feature
from sklearn.datasets import make_classification
X, y = make_classification(1000, n_features=20, n_informative=2, 
                           n_redundant=2, n_classes=2, random_state=0)
#存為dataframe格式
from pandas import DataFrame
df = DataFrame(np.hstack((X, y[:, None])),columns = range(20) + ["class"])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我們生成了一份包含1000個分類資料樣本的資料集,每個樣本有20個數值特徵。同時我們把資料儲存至pandas中的DataFrame資料結構中。我們取前幾行的資料看一眼:

df[:6]
  • 1

前6行

不幸的是,肉眼看資料,尤其是維度稍微高點的時候,很有可能看花了也看不出看不出任何線索。幸運的是,我們對於影象的理解力,比數字好太多,而又有相當多的工具可以幫助我們『視覺化』資料分佈。

我們在處理任何資料相關的問題時,瞭解資料都是很有必要的,而視覺化可以幫助我們更好地直觀理解資料的分佈和特性

資料的視覺化有很多工具包可以用,比如下面我們用來做資料視覺化的工具包Seaborn。最簡單的視覺化就是資料雜湊分佈圖和柱狀圖,這個可以用Seanborn的pairplot來完成。以下圖中2種顏色表示2種不同的類,因為20維的視覺化沒有辦法在平面表示,我們取出了一部分維度,兩兩組成pair看資料在這2個維度平面上的分佈狀況,程式碼和結果如下:

import matplotlib.pyplot as plt
import seaborn as sns
#使用pairplot去看不同特徵維度pair下資料的空間分佈狀況
_ = sns.pairplot(df[:50], vars=[8, 11, 12, 14, 19], hue="class", size=1.5)
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

pair_plot下資料分佈狀況

我們從雜湊圖和柱狀圖上可以看出,確實有些維度的特徵相對其他維度,有更好的區分度,比如第11維和14維看起來很有區分度。這兩個維度上看,資料點是近似線性可分的。而12維和19維似乎呈現出了很高的負相關性。接下來我們用Seanborn中的corrplot來計算計算各維度特徵之間(以及最後的類別)的相關性。程式碼和結果圖如下:

import matplotlib.pyplot as plt
plt.figure(figsize=(12, 10))
_ = sns.corrplot(df, annot=False)
plt.show()
  • 1
  • 2
  • 3
  • 4

各位特徵相關性

相關性圖很好地印證了我們之前的想法,可以看到第11維特徵和第14維特徵和類別有極強的相關性,同時它們倆之間也有極高的相關性。而第12維特徵和第19維特徵卻呈現出極強的負相關性。強相關的特徵其實包含了一些冗餘的特徵,而除掉上圖中顏色較深的特徵,其餘特徵包含的資訊量就沒有這麼大了,它們和最後的類別相關度不高,甚至各自之間也沒什麼先慣性。

插一句,這裡的維度只有20,所以這個相關度計算並不費太大力氣,然而實際情形中,你完全有可能有遠高於這個數字的特徵維度,同時樣本量也可能多很多,那種情形下我們可能要先做一些處理,再來實現可視化了。彆著急,一會兒我們會講到。

3.2 機器學習演算法選擇

資料的情況我們大致看了一眼,確定一些特徵維度之後,我們可以考慮先選用機器學習演算法做一個baseline的系統出來了。這裡我們繼續參照上面提到過的機器學習演算法使用圖譜。  我們只有1000個數據樣本,是分類問題,同時是一個有監督學習,因此我們根據圖譜裡教的方法,使用LinearSVC(support vector classification with linear kernel)試試。注意,LinearSVC需要選擇正則化方法以緩解過擬合問題;我們這裡選擇使用最多的L2正則化,並把懲罰係數C設為10。我們改寫一下sklearn中的學習曲線繪製函式,畫出訓練集和交叉驗證集上的得分:

from sklearn.svm import LinearSVC
from sklearn.learning_curve import learning_curve
#繪製學習曲線,以確定模型的狀況
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,
                        train_sizes=np.linspace(.1, 1.0, 5)):
    """
    畫出data在某模型上的learning curve.
    引數解釋
    ----------
    estimator : 你用的分類器。
    title : 表格的標題。
    X : 輸入的feature,numpy型別
    y : 輸入的target vector
    ylim : tuple格式的(ymin, ymax), 設定影象中縱座標的最低點和最高點
    cv : 做cross-validation的時候,資料分成的份數,其中一份作為cv集,其餘n-1份作為training(預設為3份)
    """

    plt.figure()
    train_sizes, train_scores, test_scores = learning_curve(
        estimator, X, y, cv=5, n_jobs=1, train_sizes=train_sizes)
    train_scores_mean = np.mean(train_scores, axis=1)
    train_scores_std = np.std(train_scores, axis=1)
    test_scores_mean = np.mean(test_scores, axis=1)
    test_scores_std = np.std(test_scores, axis=1)

    plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                     train_scores_mean + train_scores_std, alpha=0.1,
                     color="r")
    plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                     test_scores_mean + test_scores_std, alpha=0.1, color="g")
    plt.plot(train_sizes, train_scores_mean, 'o-', color="r",
             label="Training score")
    plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
             label="Cross-validation score")

    plt.xlabel("Training examples")
    plt.ylabel("Score")
    plt.legend(loc="best")
    plt.grid("on") 
    if ylim:
        plt.ylim(ylim)
    plt.title(title)
    plt.show()

#少樣本的情況情況下繪出學習曲線
plot_learning_curve(LinearSVC(C=10.0), "LinearSVC(C=10.0)",
                    X, y, ylim=(0.8, 1.01),
                    train_sizes=np.linspace(.05, 0.2, 5))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

學習曲線1

這幅圖上,我們發現隨著樣本量的增加,訓練集上的得分有一定程度的下降,交叉驗證集上的得分有一定程度的上升,但總體說來,兩者之間有很大的差距,訓練集上的準確度遠高於交叉驗證集。這其實意味著我們的模型處於過擬合的狀態,也即模型太努力地刻畫訓練集,一不小心把很多噪聲的分佈也擬合上了,導致在新資料上的泛化能力變差了。

3.2.1 過擬合的定位與解決

問題來了,過擬合咋辦? 針對過擬合,有幾種辦法可以處理:

  • 增大樣本量

這個比較好理解吧,過擬合的主要原因是模型太努力地去記住訓練樣本的分佈狀況,而加大樣本量,可以使得訓練集的分佈更加具備普適性,噪聲對整體的影響下降。恩,我們提高點樣本量試試:

#增大一些樣本量
plot_learning_curve(LinearSVC(C=10.0), "LinearSVC(C=10.0)",
                    X, y, ylim=(0.8, 1.1),
                    train_sizes=np.linspace(.1, 1.0, 5))
  • 1
  • 2
  • 3
  • 4

學習曲線2

是不是發現問題好了很多?隨著我們增大訓練樣本量,我們發現訓練集和交叉驗證集上的得分差距在減少,最後它們已經非常接近了。增大樣本量,最直接的方法當然是想辦法去採集相同場景下的新資料,如果實在做不到,也可以試試在已有資料的基礎上做一些人工的處理生成新資料(比如影象識別中,我們可能可以對圖片做映象變換、旋轉等等),當然,這樣做一定要謹慎,強烈建議想辦法採集真實資料。

  • 減少特徵的量(只用我們覺得有效的特徵)

比如在這個例子中,我們之前的資料視覺化和分析的結果表明,第11和14維特徵包含的資訊對識別類別非常有用,我們可以只用它們。

plot_learning_curve(LinearSVC(C=10.0), "LinearSVC(C=10.0) Features: 11&14", X[:, [11, 14]], y, ylim=(0.8, 1.0), train_sizes=np.linspace(.05, 0.2, 5))
  • 1

特徵選擇後

從上圖上可以看出,過擬合問題也得到一定程度的緩解。不過我們這是自己觀察後,手動選出11和14維特徵。那能不能自動進行特徵組合和選擇呢,其實我們當然可以遍歷特徵的組合樣式,然後再進行特徵選擇(前提依舊是這裡特徵的維度不高,如果高的話,遍歷所有的組合是一個非常非常非常耗時的過程!!):

from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest, f_classif
# SelectKBest(f_classif, k=2) 會根據Anova F-value選出 最好的k=2個特徵

plot_learning_curve(Pipeline([("fs", SelectKBest(f_classif, k=2)), # select two features
                               ("svc", LinearSVC(C=10.0))]), "SelectKBest(f_classif, k=2) + LinearSVC(C=10.0)", X, y, ylim=(0.8, 1.0), train_sizes=np.linspace(.05, 0.2, 5))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

自動特徵選擇

如果你自己跑一下程式,會發現在我們自己手造的這份資料集上,這個特徵篩選的過程超級順利,但依舊像我們之前提過的一樣,這是因為特徵的維度不太高。  從另外一個角度看,我們之所以做特徵選擇,是想降低模型的複雜度,而更不容易刻畫到噪聲資料的分佈。從這個角度出發,我們還可以有(1)多項式你和模型中降低多項式次數 (2)神經網路中減少神經網路的層數和每層的結點數 (c)SVM中增加RBF-kernel的bandwidth等方式來降低模型的複雜度。  話說回來,即使以上提到的辦法降低模型複雜度後,好像能在一定程度上緩解過擬合,但是我們一般還是不建議一遇到過擬合,就用這些方法處理,優先用下面的方法:

  • 增強正則化作用(比如說這裡是減小LinearSVC中的C引數) 正則化是我認為在不損失資訊的情況下,最有效的緩解過擬合現象的方法。
plot_learning_curve(LinearSVC(C=0.1), "LinearSVC(C=0.1)", X, y, ylim=(0.8, 1.0), train_sizes=np.linspace(.05, 0.2, 5))
  • 1

調整正則化引數

調整正則化係數後,發現確實過擬合現象有一定程度的緩解,但依舊是那個問題,我們現在的係數是自己敲定的,有沒有辦法可以自動選擇最佳的這個引數呢?可以。我們可以在交叉驗證集上做grid-search查詢最好的正則化係數(對於大資料樣本,我們依舊需要考慮時間問題,這個過程可能會比較慢):

from sklearn.grid_search import GridSearchCV
estm = GridSearchCV(LinearSVC(), 
                   param_grid={"C": [0.001, 0.01, 0.1, 1.0, 10.0]})
plot_learning_curve(estm, "LinearSVC(C=AUTO)", 
                    X, y, ylim=(0.8, 1.0),
                    train_sizes=np.linspace(.05, 0.2, 5))
print "Chosen parameter on 100 datapoints: %s" % estm.fit(X[:500], y[:500]).best_params_
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在500個點得到的結果是:{‘C’: 0.01}  使用新的C引數,我們再看看學習曲線: 

C取0.01的學習曲線

對於特徵選擇的部分,我打算多說幾句,我們剛才看過了用sklearn.feature_selection中的SelectKBest來選擇特徵的過程,也提到了在高維特徵的情況下,這個過程可能會非常非常慢。那我們有別的辦法可以進行特徵選擇嗎?比如說,我們的分類器自己能否甄別那些特徵是對最後的結果有益的?這裡有個實際工作中用到的小技巧。

我們知道:

  • l2正則化,它對於最後的特徵權重的影響是,儘量打散權重到每個特徵維度上,不讓權重集中在某些維度上,出現權重特別高的特徵。
  • 而l1正則化,它對於最後的特徵權重的影響是,讓特徵獲得的權重稀疏化,也就是對結果影響不那麼大的特徵,乾脆就拿不著權重。

那基於這個理論,我們可以把SVC中的正則化替換成l1正則化,讓其自動甄別哪些特徵應該留下權重。

plot_learning_curve(LinearSVC(C=0.1, penalty='l1', dual=False), "LinearSVC(C=0.1, penalty='l1')", X, y, ylim=(0.8, 1.0), train_sizes=np.linspace(.05, 0.2, 5))
  • 1

使用l1正則化

好了,我們一起來看看最後特徵獲得的權重:

estm = LinearSVC(C=0.1, penalty='l1', dual=False)
estm.fit(X[:450], y[:450])  # 用450個點來訓練
print "Coefficients learned: %s" % est.coef_
print "Non-zero coefficients: %s" % np.nonzero(estm.coef_)[1]
  • 1
  • 2
  • 3
  • 4

得到結果:

Coefficients learned: [[ 0.          0.          0.          0.          0.          0.01857999
   0.          0.          0.          0.004135    0.          1.05241369
   0.01971419  0.          0.          0.          0.         -0.05665314
   0.14106505  0.        ]]
Non-zero coefficients: [5 9 11 12 17 18]
  • 1
  • 2
  • 3
  • 4
  • 5

你看,5 9 11 12 17 18這些維度的特徵獲得了權重,而第11維權重最大,也說明了它影響程度最大。

3.2.2 欠擬合定位與解決

我們再隨機生成一份資料[1000*20]的資料(但是分佈和之前有變化),重新使用LinearSVC來做分類。

#構造一份環形資料
from sklearn.datasets import make_circles
X, y = make_circles(n_samples=1000, random_state=2)
#繪出學習曲線
plot_learning_curve(LinearSVC(C=0.25),"LinearSVC(C=0.25)",X, y, ylim=(0.5, 1.0),train_sizes=np.linspace(.1, 1.0, 5))
  • 1
  • 2
  • 3
  • 4
  • 5

環形資料的學習曲線

簡直爛出翔了有木有,二分類問題,我們做隨機猜測,準確率都有0.5,這比隨機猜測都高不了多少!!!怎麼辦?

不要盲目動手收集更多資料,或者調整正則化引數。我們從學習曲線上其實可以看出來,訓練集上的準確度和交叉驗證集上的準確度都很低,這其實就對應了我們說的『欠擬合』狀態。別急,我們回到我們的資料,還是視覺化看看:

f = DataFrame(np.hstack((X, y[:, None])), columns = range(2) + ["class"])
_ = sns.pairplot(df, vars=[0, 1], hue="class", size=3.5)
  • 1
  • 2

環形資料視覺化

你發現什麼了,資料根本就沒辦法線性分割!!!,所以你再找更多的資料,或者調整正則化引數,都是無濟於事的!!!

那我們又怎麼解決欠擬合問題呢?通常有下面一些方法:

  • 調整你的特徵(找更有效的特徵!!) 比如說我們觀察完現在的資料分佈,然後我們先對資料做個對映:
# 加入原始特徵的平方項作為新特徵
X_extra = np.hstack((X, X[:, [0]]**2 + X[:, [1]]**2))

plot_learning_curve(LinearSVC(C=0.25), "LinearSVC(C=0.25) + distance feature", X_extra, y, ylim=(0.5, 1.0), train_sizes=np.linspace(.1, 1.0, 5))
  • 1
  • 2
  • 3
  • 4

平方對映後的準確度

臥槽,少年,這準確率,被嚇尿了有木有啊!!!所以你看,選用的特徵影響太大了,當然,我們這裡是人工模擬出來的資料,分佈太明顯了,實際資料上,會比這個麻煩一些,但是在特徵上面下的功夫還是很有回報的。

  • 使用更復雜一點的模型(比如說用非線性的核函式) 我們對模型稍微調整了一下,用了一個複雜一些的非線性rbf kernel:
from sklearn.svm import SVC
# note: we use the original X without the extra feature
plot_learning_curve(SVC(C=2.5, kernel="rbf", gamma=1.0), "SVC(C=2.5, kernel='rbf', gamma=1.0)",X, y, ylim=(0.5, 1.0), train_sizes=np.linspace(.1, 1.0, 5))
  • 1
  • 2
  • 3

rbf核SVM學習曲線

你看,效果依舊很贊。

3.3 關於大資料樣本集和高維特徵空間

我們在小樣本的toy dataset上,怎麼搗鼓都有好的方法。但是當資料量和特徵樣本空間膨脹非常厲害時,很多東西就沒有那麼好使了,至少是一個很耗時的過程。舉個例子說,我們現在重新生成一份資料集,但是這次,我們生成更多的資料,更高的特徵維度,而分類的類別也提高到5。

3.3.1 大資料情形下的模型選擇與學習曲線

在上面提到的那樣一份資料上,我們用LinearSVC可能就會有點慢了,我們注意到機器學習演算法使用圖譜推薦我們使用SGDClassifier。其實本質上說,這個模型也是一個線性核函式的模型,不同的地方是,它使用了隨機梯度下降做訓練,所以每次並沒有使用全部的樣本,收斂速度會快很多。再多提一點,SGDClassifier對於特徵的幅度非常敏感,也就是說,我們在把資料灌給它之前,應該先對特徵做幅度調整,當然,用sklearn的StandardScaler可以很方便地完成這一點。

SGDClassifier每次只使用一部分(mini-batch)做訓練,在這種情況下,我們使用交叉驗證(cross-validation)並不是很合適,我們會使用相對應的progressive validation:簡單解釋一下,estimator每次只會拿下一個待訓練batch在本次做評估,然後訓練完之後,再在這個batch上做一次評估,看看是否有優化。

#生成大樣本,高緯度特徵資料
X, y = make_classification(200000, n_features=200, n_informative=25, n_redundant=0, n_classes=10, class_sep=2, random_state=0)

#用SGDClassifier做訓練,並畫出batch在訓練前後的得分差
from sklearn.linear_model import SGDClassifier
est = SGDClassifier(penalty="l2", alpha=0.001)
progressive_validation_score = []
train_score = []
for datapoint in range(0, 199000, 1000):
    X_batch = X[datapoint:datapoint+1000]
    y_batch = y[datapoint:datapoint+1000]
    if datapoint > 0:
        progressive_validation_score.append(est.score(X_batch, y_batch))
    est.partial_fit(X_batch, y_batch, classes=range(10))
    if datapoint > 0:
        train_score.append(est.score(X_batch, y_batch))

plt.plot(train_score, label="train score")
plt.plot(progressive_validation_score, label="progressive validation score")
plt.xlabel("Mini-batch")
plt.ylabel("Score")
plt.legend(loc='best')  
plt.show()                     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

得到如下的結果: 

SGDClassifier學習曲線

從這個圖上的得分,我們可以看出在50個mini-batch迭代之後,資料上的得分就已經變化不大了。但是好像得分都不太高,所以我們猜測一下,這個時候我們的資料,處於欠擬合狀態。我們剛才在小樣本集合上提到了,如果欠擬合,我們可以使用更復雜的模型,比如把核函式設定為非線性的,但遺憾的是像rbf核函式是沒有辦法和SGDClassifier相容的。因此我們只能想別的辦法了,比如這裡,我們可以把SGDClassifier整個替換掉了,用多層感知神經網來完成這個任務,我們之所以會想到多層感知神經網,是因為它也是一個用隨機梯度下降訓練的演算法,同時也是一個非線性的模型。當然根據機器學習演算法使用圖譜,也可以使用核估計(kernel-approximation)來完成這個事情。

3.3.2 大資料量下的視覺化

大樣本資料的視覺化是一個相對比較麻煩的事情,一般情況下我們都要用到降維的方法先處理特徵。我們找一個例子來看看,可以怎麼做,比如我們資料集取經典的『手寫數字集』,首先找個方法看一眼這個圖片資料集。

#直接從sklearn中load資料集
from sklearn.datasets import load_digits
digits = load_digits(n_class=6)
X = digits.data
y = digits.target
n_samples, n_features = X.shape
print "Dataset consist of %d samples with %d features each" % (n_samples, n_features)

# 繪製數字示意圖
n_img_per_row = 20
img = np.zeros((10 * n_img_per_row, 10 * n_img_per_row))
for i in range(n_img_per_row):
    ix = 10 * i + 1
    for j in range(n_img_per_row):
        iy = 10 * j + 1
        img[ix:ix + 8, iy:iy + 8] = X[i * n_img_per_row + j].reshape((8, 8))

plt.imshow(img, cmap=plt.cm.binary)
plt.xticks([])
plt.yticks([])
_ = plt.title('A selection from the 8*8=64-dimensional digits dataset')
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

數字示意圖

我們總共有1083個訓練樣本,包含手寫數字(0,1,2,3,4,5),每個樣本圖片中的畫素點平鋪開都是64位,這個維度顯然是沒辦法直接視覺化的。下面我們基於scikit-learn的示例教程對特徵用各種方法做降維處理,再視覺化。

隨機投射 我們先看看,把資料隨機投射到兩個維度上的結果:

#import所需的package
from sklearn import (manifold, decomposition, random_projection)
rp = random_projection.SparseRandomProjection(n_components=2, random_state=42)

#定義繪圖函式
from matplotlib import offsetbox
def plot_embedding(X, title=None):
    x_min, x_max = np.min(X, 0), np.max(X, 0)
    X = (X - x_min) / (x_max - x_min)

    plt.figure(figsize=(10, 10))
    ax = plt.subplot(111)
    for i in range(X.shape[0]):
        plt.text(X[i, 0], X[i, 1], str(digits.target[i]),
                 color=plt.cm.Set1(y[i] / 10.),
                 fontdict={'weight': 'bold', 'size': 12})

    if hasattr(offsetbox, 'AnnotationBbox'):
        # only print thumbnails with matplotlib > 1.0
        shown_images = np.array([[1., 1.]])  # just something big
        for i in range(digits.data.shape[0]):
            dist = np.sum((X[i] - shown_images) ** 2, 1)
            if np.min(dist) < 4e-3:
                # don't show points that are too close
                continue
            shown_images = np.r_[shown_images, [X[i]]]
            imagebox = offsetbox.AnnotationBbox(
                offsetbox.OffsetImage(digits.images[i], cmap=plt.cm.gray_r),
                X[i])
            ax.add_artist(imagebox)
    plt.xticks([]), plt.yticks([])
    if title is not None:
        plt.title(title)

#記錄開始時間
start_time = time.time()
X_projected = rp.fit_transform(X)
plot_embedding(X_projected, "Random Projection of the digits (time: %.3fs)" % (time.time() - start_time))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

結果如下: 

2方向隨機投射圖

PCA降維 在維度約減/降維領域有一個非常強大的演算法叫做PCA(Principal Component Analysis,主成分分析),它能將原始的絕大多數資訊用維度遠低於原始維度的幾個主成分表示出來。PCA在我們現在的資料集上效果還不錯,我們來看看用PCA對原始特徵降維至2維後,原始樣本在空間的分佈狀況:

from sklearn import (manifold, decomposition, random_projection)
#TruncatedSVD 是 PCA的一種實現
X_pca = decomposition.TruncatedSVD(n_components=2).fit_transform(X)
#記錄時間
start_time = time.time()
plot_embedding(X_pca,"Principal Components projection of the digits (time: %.3fs)" % (time.time() - start_time))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

得到的結果如下: 

PCA後的視覺化

我們可以看出,效果還不錯,不同的手寫數字在2維平面上,顯示出了區域集中性。即使它們之間有一定的重疊區域。

如果我們用一些非線性的變換來做降維操作,從原始的64維降到2維空間,效果更好,比如這裡我們用到一個技術叫做t-SNE,sklearn的manifold對其進行了實現:

from sklearn import (manifold, decomposition, random_projection)
#降維
tsne = manifold.TSNE(n_components=2, init='pca', random_state=0)
start_time = time.time()
X_tsne = tsne.fit_transform(X)
#繪圖
plot_embedding(X_tsne,
               "t-SNE embedding of the digits (time: %.3fs)" % (time.time() - start_time))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

非線性降維手寫數字分佈圖

我們發現結果非常的驚人,似乎這個非線性變換降維過後,僅僅2維的特徵,就可以將原始資料的不同類別,在平面上很好地劃分開。不過t-SNE也有它的缺點,一般說來,相對於線性變換的降維,它需要更多的計算時間。也不太適合在大資料集上全集使用。

3.4 損失函式的選擇

損失函式的選擇對於問題的解決和優化,非常重要。我們先來看一眼各種不同的損失函式:

import numpy as np
import matplotlib.plot as plt
# 改自http://scikit-learn.org/stable/auto_examples/linear_model/plot_sgd_loss_functions.html
xmin, xmax = -4, 4
xx = np.linspace(xmin, xmax, 100)
plt.plot([xmin, 0, 0, xmax], [1, 1, 0, 0], 'k-',
         label="Zero-one loss")
plt.plot(xx, np.where(xx < 1, 1 - xx, 0), 'g-',
         label="Hinge loss")
plt.plot(xx, np.log2(1 + np.exp(-xx)), 'r-',
         label="Log loss")
plt.plot(xx, np.exp(-xx), 'c-',
         label="Exponential loss")
plt.plot(xx, -np.minimum(xx, 0), 'm-',
         label="Perceptron loss")

plt.ylim((0, 8))
plt.legend(loc="upper right")
plt.xlabel(r"Decision function $f(x)$")
plt.ylabel("$L(y, f(x))$")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

得到結果影象如下:

損失函式對比

不同的損失函式有不同的優缺點:

  • 0-1損失函式(zero-one loss)非常好理解,直接對應分類問題中判斷錯的個數。但是比較尷尬的是它是一個非凸函式,這意味著其實不是那麼實用。
  • hinge loss(SVM中使用到的)的健壯性相對較高(對於異常點/噪聲不敏感)。但是它沒有那麼好的概率解釋。
  • log損失函式(log-loss)的結果能非常好地表徵概率分佈。因此在很多場景,尤其是多分類場景下,如果我們需要知道結果屬於每個類別的置信度,那這個損失函式很適合。缺點是它的健壯性沒有那麼強,相對hinge loss會對噪聲敏感一些。
  • 多項式損失函式(exponential loss)(AdaBoost中用到的)對離群點/噪聲非常非常敏感。但是它的形式對於boosting演算法簡單而有效。
  • 感知損失(perceptron loss)可以看做是hinge loss的一個變種。hinge loss對於判定邊界附近的點(正確端)懲罰力度很高。而perceptron loss,只要樣本的判定類別結果是正確的,它就是滿意的,而不管其離判定邊界的距離。優點是比hinge loss簡單,缺點是因為不是max-margin boundary,所以得到模型的泛化能力沒有hinge loss強。

4. 總結

全文到此就結束了。先走馬觀花看了一遍機器學習的演算法,然後給出了對應scikit-learn的『祕密武器』機器學習演算法使用圖譜,緊接著從瞭解資料(視覺化)選擇機器學習演算法定位過/欠擬合及解決方法大量極的資料視覺化損失函式優缺點與選擇等方面介紹了實際機器學習問題中的一些思路和方法。本文和文章機器學習系列(3)_邏輯迴歸應用之Kaggle泰坦尼克之災都提及了一些處理實際機器學習問題的思路和方法,有相似和互補之處,歡迎大家參照著看。

--------------------- 本文來自 寒小陽 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/han_xiaoyang/article/details/50469334?utm_source=copy