官术网_书友最值得收藏!

2.2 訓練一個傳統的機器學習模型

運用機器學習方法分析數據、建立預測模型本身是一個非常復雜的過程,很難在較短的篇幅內完全說清楚,所以這里我們將會結合一個簡單的實戰案例,將基本概念結合代碼實現出來。同時,本節也將穿插介紹如何使用Python的數據科學套裝,如表2-2所示。

表2-2 使用Python數據的科學套裝

如果使用了第1章中指定的開發環境,這里直接使用就好,否則需要注意一下,版本號的偏差可能會造成程序報告警告,說某一用法是以前的,未來版本會拋棄,也有可能會直接報錯。如果本章接下來的程序提示錯誤,請大家首先確認使用的是否為指定的開發環境以及正確的版本號。

2.2.1 第一步,觀察數據

我們這里使用sklearn官方提供的鳶尾花分類數據集。這個數據集最初是埃德加·安德森從加拿大加斯帕半島上的鳶尾屬花朵中提取的地理變異數據,包含150個樣本,屬于鳶尾屬下的三個亞屬,即山鳶尾(setosa)、變色鳶尾(versicolor)和維吉尼亞鳶尾(virginica)。四個特征被用作樣本的定量分析,分別是花萼(Sepal)和花瓣(Petal)的長度和寬度。這個案例的目的是通過建立一種數學模型,嘗試使用四個特征去預測某一鳶尾花屬于哪一個亞種。具體如何建立這種模型,接下來我們將會逐步講解。

要建立模型,首先需要觀察數據,而觀察數據的第一步是要閱讀數據相關的說明文檔,弄清楚我們拿到的數據是哪一種具體格式。雖然我們知道了數據有多少個樣本、多少種特征,但是目前還不清楚數據是以什么格式給我們的,是文件還是數據庫,或者是一個python對象,所以需要去sklearn官網查閱說明文檔,地址是http://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_iris.html。

文檔提到返回的對象如下:

文檔信息寫到,這是一個Python對象(object),并且具有字典(dictionary)的特征。具體的調用方法如下:

    from sklearn.datasets import load_iris
    data = load_iris()

2.2.2 第二步,預覽數據

既然上一步提到這是一個具有字典特征的對象,我們就可以用Python字典調用的方法對數據有一個總體的預覽。

這里首先簡單介紹Python的字典。Python主要包括列表、元組以及字典這幾種較為高級的數據結構,如果讀者熟悉C++,其實就是C++標準庫里的vector、set以及unordered_map。字典結構其實就是一個鍵值對(unordered_map),但這里的鍵值對相比C++而言,用起來相對容易一些,可以方便地指向字符串,甚至列表、數組和其他字典:

運行結果:

          #out:
          0
          [1, 2, 3] 2
          4

可以使用for循環遍歷整個字典:

    for k in map_abc:
          print("key:",k, "\nvalue:", map_abc[k])

運行結果:

          #out:
          key: b
          value: [1, 2, 3]
          key: a
          value: 0
          key: c
          value: {'cc': 4}

同樣,也來遍歷我們的數據:

data.data就是需要的150×4的輸入特征矩陣,四個特征存在data.feature_names里。分類標簽在data.target中,其中的0、1、2分別代表data.target_names里面的setosa、versicolor以及virginica。由此,我們完成了數據的基本預覽。

實際上,在預覽階段還有很多問題需要思考:

  • 不同分類的樣本分類是否均勻?
  • 數據是否受到極值、缺失值的影響?
  • 能否不建立模型就看出三者的分類關系?
  • 能否對樣本的分類情況進行簡單的預覽?

這些問題可以用很簡單的程序回答。其實我們想更快地回答這些問題,因此接下來將引入Python數據分析套裝,用pandas對數據進行類似SQL的合理結構化,然后基于可視化分析庫matplotlib與seaborn,通過圖形回答這些問題。

對矩陣格式的數據進行結構化處理,轉換為pandas的dataframe。

    df = pd.DataFrame(data.data)
    df.columns = data.feature_names
    df['species'] = [ data['target_names'][x] for x in data.target ]
    df.head()

不同分類的樣本分類是否均勻

對species分組計數:

    df_cnt = df['species'].value_counts().reset_index()
    df_cnt

結果如下:(注意這里不加reset_index的話,將不會返回數據框的形式,不方便制圖。)

對結果做圖:

    sns.barplot(data=df_cnt, x='index', y='species')

其結果如圖2-2所示。

圖2-2 分組計數結果做圖效果

數據是否受到極值缺失值的影響

極值、缺失值會影響對數據的整體認識,對模型產生干擾,因此,我們在正式分析數據之前,首先應該關注它們會產生什么樣的影響。這里的極值是指相比其他數字而言,非常大或者非常小的值,又稱為離群值,既可能是由于收集階段的錯誤造成的,也可能是由于一些意外因素造成的。極值會對數據分析結果造成一定干擾,典型的例子如同國家統計局發布人均年收入數據時,很多人發現自己“被平均”,因此對于極值,會根據實際需求選擇是否剔除。

缺失值同樣也會有影響。比如小明給自己量體溫,量了一周的體溫值:

問小明這一周的平均體溫是多少?這種情況下,真實的平均體溫我們是不知道的,因為不知道小明周四的體溫是多少,所以整周的平均值也是不知道的:

    import numpy as np
    np_temp = [36.2, 36.3, 36.4, np.nan, 36.3, 36.2, 36.4]
    np.mean(np_temp)

運行結果:

          # out:
            nan

遇到這種情況,實際上是對數據的一種浪費,因為周四沒有收集到體溫數據,這一周其他6天的數據就失去了作用。在大規模的數據中,我們很難保證所有數據都被合理地收集到,此時如果有幾萬個數據,因為個別的缺失就全部扔掉,這種浪費就更加明顯了。對于這種情況,通常的做法是在計算的情況下不考慮未知量,或者是用平均值、中位數去填補未知值。

方法1 不考慮周四

    np_temp = [36.2, 36.3, 36.4, np.nan, 36.3, 36.2, 36.4]
    np.nanmean(np_temp)

運行結果:

          # out
          36.3...

方法2 用其他數字平均值填補缺失值后計算

    np_temp[3] = np.nanmean(np_temp)
    np.mean(np_temp)

運行結果:

    # out
    36.3...

方法3 用數字0填補缺失值后計算(大錯特錯,不要這樣)

    np_temp[3] = 0
    np.mean(np_temp)

之所以把這一條寫上來,是因為現實中真的有人會用0直接填補缺失值。對這種情況,不是說不行,很多時候其實可以用0填補,比如統計某人口稀少地區的各種汽車銷量,如果某一汽車銷量缺失,我們是可以用0填補的,因為這個數字可能真的很小,可能真的是0,以至于統計人員收集數據時直接忽略了這種汽車。但小明周三的體溫,實在不太像是0度,因此這里不可以用0來填補缺失值。

介紹完極值與缺失值將會帶來的問題后,我們想知道如何用最快的速度判斷數據里面是否存在這兩種情況,最簡單的辦法就是對數據框執行.describe()操作:

    df.describe()

運行結果:

      # out:

這里的極值(min/max)看起來并不明顯,平均值、標準差范圍也比較接近,并且不存在數據缺失。如果有缺失,最后一行會單獨顯示缺失了幾個值,繼而我們進一步確認這四個特征是否為正態分布。這種檢驗通常使用QQ-Plot,即橫坐標是標準正態分布的Quantile(如圖2-3所示的x軸),縱坐標是樣本實際分布的Quantile,如果實際分布是正態分布,兩者之間就會存在線性的關系。

圖2-3 橫坐標是標準正態分布的Quantile

對我們的四個特征進行正態分布檢驗:

運行結果如圖2-4所示。

這里的前兩個特征可以認為來自同一個正態分布,如此很多統計假設(如t檢驗)等就可以成立,繼而在后續的建模階段方便我們使用很多有效的算法。后兩個特征更像是兩條線中間斷開了,似乎是來自兩個正態分布。這種情況未必是壞事,如果某個分類種類完全來自其中一個正態分布,其他的來自另一個正態分布,那么這個特征就成了一個非常具有區分度的特征,需要我們重點關注。

圖2-4 四個特征的正態分布

因此下一步的目標就是求具體每個分類的平均值、方差,如何做呢?這里當然可以提取不同分類對應的子矩陣,然后計算子矩陣的平均值、方差,讀者可以自行嘗試。我們這里提供另一種基于透視表(pivot_table)的方法。首先,對于150×4的二維矩陣特征,將其變為600×1的一維矩陣特征:

    pd.melt(df, id_vars=['species'])

運行結果:

          # out:

然后將這個一維矩陣特征通過透視表操作,針對每個特征值,以分類結果為行名稱,求它們的平均值以及方差:

運行結果:

      # out:

我們關注petal length和petal width兩個特征,發現二者在setosa這個種類中要小于其他兩個種類,并且setosa與versicolor之間的距離(4.260-1.464)也遠高于三倍方差值(0.0301×3+0.2208×3),正好對應在圖2-4中的兩個間隔較遠的正態分布。

繼而我們以第三個特征為例,對其不同組分別進行正態分布檢驗:

其結果如圖2-5所示。

圖2-5 第三個特征的正態分布

由此可見,該特征在不同組內部是符合正態分布的。

能不能不建立模型,就看出三者的分類關系

首先,這里說一下“不建立模型”的邏輯是什么。不建立模型就看出分類關系,是因為有時候分類的定義很簡單,用復雜模型多少有點“殺雞用牛刀”的意味。比如判斷一個人是否酒駕,就一個指標,每百毫升血液里酒精含量是否大于20mg,大于就是酒駕,否則不是,就是一個簡單的if…else判斷關系,不需要復雜的模型。實際工作中,如果找到了這種簡單的if…else標準,其實是件好事,首先我們不需要費時費力地挖特征、訓練模型,其次得出的結論也很簡單,不涉及復雜的組合,也利于被人接受。

我們的鳶尾花數據集是否存在這種簡單標準?可以借助簡單的可視化技術來看一眼:

    sns.pairplot(df, hue="species")

運行結果如圖2-6所示。

圖2-6 鳶尾花數據集的可視化

這個圖包含了兩層信息:第一層是單一值是否足以分開不同的分類結果,就是對角線上的四個分布圖;第二層是數據間的兩兩組合是否足以分開不同的分類結果,就是非對角線上的散點圖,其中左下角與右上角部分x - y坐標軸是對稱的。

對于第一層信息,我們注意到第三行、第三列的點圖中setosa與其他兩類在petal length這個特征上是可以完全區分開的,即petal length小于2的是setosa、大于2的是其他兩種分類。另外兩個種類區分得并不好,有很多的重疊部分。

對于第二層信息,根據點圖判斷的話,所有第三行、第三列的點圖中,setosa與其他兩個種類也是可以完全區分開的。因為這些圖是特征的兩兩組合,這些組合都使用第一層信息就可以明確區分setosa的特征——petal length,自然在兩兩組合中也可以區分。通過兩兩組合,另外兩種也并非完全分開,但是重疊部分的交集有多有少——sepal的長寬重合得多,petal的長寬重合就少得多。

這時候讀者會有新的問題了——既然兩個兩個組合,看起來后兩類重疊的面積少了很多,那么特征如果進行三個組合,畫三維立體圖重疊的是否會更少?這里不太好畫三維的點圖,讀者可以畫四個三維的圖看一眼。這里之所以不畫并非是筆者懶,而是既然可以三個特征組合,就可以四個一起組合,這種情況下就沒法畫圖了,畢竟我們生活在一個三維的宏觀世界,要表示一個四維的坐標系難度其實挺大的。

因此,我們這里的結論是,如果想找出setosa,那么不用建模也可以得出結論,花瓣(petal)長度小于2cm的就是。如果想找出其他兩種,結論就不是肯定的了。

當然,對于這種直觀方法的局限性,讀者應該也發現了,就是特征兩兩組合會造成維度災難。我們這里是四個特征,兩兩組合就會得到4×3÷2 = 6種不同的組合方式。如果是幾百個特征,就是上萬種組合方式,幾萬特征就是上億種組合方式。這種情況下,要是再畫這種組合圖,人工找出特征組合就不現實了。我們需要計算機幫助組合特征,而計算機組合特征的方法就是借助機器學習模型。

對于各種機器學習模型,本書的篇幅主要是介紹監督學習方法,就是之前提到的應試教育。在此之前,我們先簡單介紹下非監督學習找組合特征的方法,并且基于非監督學習的特征組合,對數據進行一個分類結果的預覽。

能否對樣本的分類情況進行簡單的預覽

上文提到,直接將特征以兩兩組合的方式畫在二維平面上,在特征過多時,會由于組合過多使得這種方法難以發揮作用。這時能否直接在二維平面上看到四維的分布情況呢?可以通過降維做到。

提到“降維”,最經典的一段文學描述莫過于《三體》這部科幻小說,在最后階段描寫的外星人使用“二向箔”,對太陽系進行的一場降維打擊:

在二維化的太空艇上,可以看到二維展開后的三維構造,可以分辨出座艙和聚變發動機等部位,還有座艙中那個卷曲的人體。在另一個二維化的人體上,可以清楚地分辨出骨骼和脈絡,也可以認出身體的各個部位。在二維化的過程中,三維物體上的每個點都按照精確的幾何規則投射到二維平面上,以至于這個二維體成為三維太空艇和三維人體的兩張最完整最精確的圖紙,其所有的內部結構都在平面上排列出來,沒有任何隱藏。

這是一段非常具有啟發意義的描述。因為其實降維打擊可以很容易,外星人完全可以用一個巨大的蒼蠅拍直接拍扁整個太陽系,不過這樣就做不到“其所有的內部結構都在平面上排列出來,沒有任何隱藏”。可見降維十分容易,降維同時盡可能地保留原有的結構難,例如前面的seaborn.pairplot實際上就是進行了12次四維到二維的降維,每次都用了其中的兩組特征,然后扔掉了剩下的兩組。于是我們就思考如何盡可能多地在二維平面保留高維特征背后的信息。

主成分分析(PCA)是其中的一種思路。PCA有兩種通俗易懂的解釋,一是最大化投影后數據的方差,即讓數據在投影到的平面上更分散;二是最小化投影造成的損失,即讓數據到投影平面的垂直距離最小。更通俗地說,就是PCA實際上是在尋找一個能把蒼蠅在墻上拍得最扁、展開面積最大的一種角度。這種降維方法與二向箔給太陽系降維時使用的展示所有細節的黑科技降維方法當然沒法比,但也非常有用,可以將其運用在鳶尾花的數據集上。

這種算法運用在鳶尾花數據集上的效果如下。需要讀者的注意是,為了方便展示,這里用了鳶尾花數據集的前三個特征進行主成分分析,實際運用中需要展示全部特征。

其運行結果如圖2-7所示。

圖2-7 前三個特征進行主成分分析

如果希望知道投影平面是什么,可以通過協方差矩陣得到。

其運行結果如圖2-8所示。

圖2-8 通過協方差矩陣得到投影平面

由此可見,這里找到的投影平面是數據展開效果非常好的一個平面,符合PCA定義的預期。

最后,請讀者思考PCA的定義。首先,投影平面是否必須是一個平面?能否是一個曲面,比如廣義相對論里那種扭曲的空間。其次,這里“最大化投影后數據的方差”的目標,是否可以修改?答案當然是肯定的,這部分的詳細介紹請進一步學習“流形學習”(manifold learning)相關的內容。這些方法在我們的數據集上的簡單使用效果如下:

其運行結果如圖2-9所示。

圖2-9 運行結果

主站蜘蛛池模板: 龙南县| 左云县| 白山市| 定陶县| 永仁县| 新巴尔虎左旗| 万年县| 亳州市| 神农架林区| 慈利县| 綦江县| 腾冲县| 台东县| 永安市| 宁陵县| 基隆市| 定陶县| 拜泉县| 澄城县| 霍邱县| 咸丰县| 沛县| 佛冈县| 都江堰市| 靖远县| 嘉鱼县| 汉阴县| 凌海市| 汨罗市| 夏河县| 屯留县| 大庆市| 汶上县| 比如县| 北川| 大同县| 方山县| 峨眉山市| 嘉义县| 织金县| 大荔县|