python劃分訓練集、驗證集和測試集

機器學習簡單流程:

  1. 使用大量和任務相關的數據集來訓練模型;
  2. 通過模型在數據集上的誤差不斷迭代訓練模型,得到對數據集擬合合理的模型;
  3. 將訓練好調整好的模型應用到真實的場景中;

我們最終的目的是將訓練好的模型部署到真實的環境中,希望訓練好的模型能夠在真實的數據上得到好的預測效果,換句話說就是希望模型在真實數據上預測的結果誤差越小越好。我們把模型在真實環境中的誤差叫做泛化誤差最終的目的是希望訓練好的模型泛化誤差越低越好

我們希望通過某個信號來了解模型的泛化誤差,這樣就可以指導我們得到泛化能力更強的模型:

  1. 使用泛化誤差本身。這是很自然的想法,我們訓練模型的最終目的就是希望模型的泛化誤差最低,當然可以使用泛化誤差本身來作爲檢測信號。如果泛化誤差小的話還可以接受,但是通常情況下沒有那麼幸運,泛化誤差可能很大,這個時候你肯定會將部署的模型撤回,重新訓練,你可能需要部署和訓練之間往復很多次,這種方式雖然能夠更好的指導我們的模型,但是成本和效率非常的差;
  2. 使用模型在數據集上訓練的擬合程度來作爲評估模型的信號。但是往往我們獲取的數據集並不是完全的乾淨以及有代表性,通常我們獲取到的數據集可能很少、數據的代表性不夠、包含太多的噪聲或者是被一些無關特徵污染,我們獲取到的數據集或多或少都會有這些問題,那麼模型對訓練數據集的擬合程度不能指導泛化誤差,也就是說訓練的時候擬合的好並不代表模型的泛化誤差就小,你甚至可以將模型在數據集上的誤差減小到0,但是因爲對模型訓練時候的數據集往往不乾淨,所以這樣的模型並不代表泛化能力就強。

1.訓練集與測試集

前面說到我們既不能通過直接將泛化誤差作爲了解模型泛化能力的信號,因爲在部署環境和訓練模型之間往復,代價很高,也不能使用模型對訓練數據集的擬合程度來作爲了解模型泛化能力的信號,因爲我們獲得的數據往往不乾淨。

更好的方式就是將數據分割成兩部分:訓練集和測試集。我們可以使用訓練集的數據來訓練模型,然後用測試集上的誤差作爲最終模型在應對現實場景中的泛化誤差。有了測試集,我們想要驗證模型的最終效果,只需將訓練好的模型在測試集上計算誤差,即可認爲此誤差即爲泛化誤差的近似,我們只需讓我們訓練好的模型在測試集上的誤差最小即可。

這裏有幾點需要注意:

  1. 通常將數據集的80%作爲訓練集,20%作爲測試集;
  2. 通常需要在開始構建模型之前把數據集進行劃分,防止數據窺探偏誤,也就是說我們避免瞭解太多關於測試集中的樣本特點,防止我們認爲的挑選有助於測試集數據的模型,這樣的結果會過於樂觀,但是實際上並沒有預期的那樣優秀;
  3. 通常我們在構建模型的時候需要將數據進行處理,包括一些數據的清洗,數據的特徵縮放(標準化或者歸一化),此時我們只需要在訓練集上進行這些操作,然後將其在訓練集上得到的參數應用到測試集中,也就是說,在工作流程中,你不能使用在測試數據集上計算的得到的任何結果。比如:我們得到的屬性中可能有缺失值,因爲在這些操作之前,我們已經把數據集分成了訓練集和測試集,通常的做法是通過計算屬性值的中位數來填充缺失值,注意此時計算屬性值的中位數是通過訓練集上的數據進行計算的,當我們得到一個模型的時候,如果想要測試模型的測試誤差來近似泛化誤差的時候,可能此時的測試集也會有一些缺失值,此時對應屬性的缺失值是通過訓練集計算的中位數來進行填充的;
  4. 由於測試集作爲對泛化誤差的近似,所以訓練好模型,最後在測試集上近似估計模型的泛化能力。此時假設有兩個不同的機器學習模型,猶豫不決的時候,可以通過訓練兩個模型,然後對比他們在測試數據上的泛化誤差,選擇泛化能力強的模型。

前面說了這麼多,那如何劃分數據集爲訓練集和測試集呢?其實很簡單,可以自己編寫程序,也可以使用sklearn提供的模塊:

通過簡單代碼實現:

import numpy as np
def split_train_test(data,test_ratio):
    #設置隨機數種子,保證每次生成的結果都是一樣的
    np.random.seed(42)
    #permutation隨機生成0-len(data)隨機序列
    shuffled_indices = np.random.permutation(len(data))
    #test_ratio爲測試集所佔的半分比
    test_set_size = int(len(data)) * test_ratio
    test_indices = shuffled_indices[:test_ratio]
    train_indices = shuffled_indices[test_set_size:]
    #iloc選擇參數序列中所對應的行
    return data.iloc[train_indices],data.iloc[test_indices]

#測試
train_set,test_set = split_train_test(data,0.2)
print(len(train_set), "train +", len(test_set), "test")

通過sklearn實現:

from sklearn.model_selection import train_test_split
#data:需要進行分割的數據集
#random_state:設置隨機種子,保證每次運行生成相同的隨機數
#test_size:將數據分割成訓練集的比例
train_set, test_set = train_test_split(data, test_size=0.2, random_state=42)

前面介紹的兩種分割數據集的方式都是採用純隨機的採樣方式,這種方式對於大量數據集以及對於目標值分佈均勻的情況是可行的。比如對於分類任務,我們訓練一個二值分類器,可能數據中包含大量的正例樣本,僅僅包含10%的反例樣本,此時的標籤分佈很不均勻,如果我們通過隨機採樣的方式,極端情況下可能將正例樣本都劃分到訓練集上,而反例樣本恰好都分到測試集,這樣訓練出來的模型,效果一定不會太好,所以我們需要採用分層採樣的方式進行劃分數據集,也就是說保證訓練集中既包含一定比例的正例樣本又要包含一定比例的負例樣本。

幸運的是sklearn提供了我們分層抽樣的函數,在這之前先看看官方提供的例子:

from sklearn.model_selection import StratifiedShuffleSplit
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([0, 0, 1, 1])
split = StratifiedShuffleSplit(n_splits=3, test_size=0.5, random_state=0)
print(split )       # doctest: +ELLIPSIS

# StratifiedShuffleSplit(n_splits=3, random_state=0, ...)
for train_index, test_index in split.split(X, y):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

"""
StratifiedShuffleSplit(n_splits=3, random_state=0, test_size=0.5,train_size=None)
TRAIN: [1 2] TEST: [3 0]
TRAIN: [0 2] TEST: [1 3]
TRAIN: [0 2] TEST: [3 1]
"""

通過上面的例子我們可以瞭解使用分層進行劃分數據集的大概流程,以及各個參數的含義:

  • n_splits:分割迭代的次數,如果我們要劃分訓練集和測試集的話,將其設置爲1即可;
  • test_size:分割測試集的比例;
  • random_state:設置隨機種子;

下面通過兩種方式對原始的mnist數據集進行劃分,首先要準備數據集:

from sklearn.datasets import fetch_mldata
#我將最原始的mnist數據集下載到當前路徑下,指定data_home
mnist = fetch_mldata('MNIST original',data_home=r"./")
x_data = mnist["data"].reshape((mnist["data"].shape[0],-1))
y_data = mnist["target"].reshape((mnist["target"].shape[0],-1))
print(x_data.shape) #(70000, 784)
print(y_data.shape) #(70000, 1)
  • 使用隨機採樣的方式分割數據集:
#使用隨機採樣方式劃分數據集
from sklearn.model_selection import train_test_split
import numpy as np

data = np.hstack((x_data,y_data))
#先將數據集進行拼接,要不然我們只針對樣本進行採樣的話,會找不到對應的標籤的
train_set,test_set = train_test_split(data,test_size = 0.2,random_state = 42)
print(len(train_set),len(test_set))

"""
56000 14000
"""
  • 使用分層採樣的方式分割數據集:
from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits = 1,test_size = 0.2,random_state = 42)

#根據mnist["target"]來進行分層採樣
for train_index,test_index in split.split(data,data[:,-1]):
    train_set = data[train_index,:]
    test_set = data[test_index,:]

print(len(train_set),len(test_set))

"""
56000 14000
"""

如果想要知道抽取的各個樣本的比例,你可以將數據轉換成DataFrame對象(當然在處理數據的開始你也可以將數據轉換爲DataFrame方便操作):

#將分割後的訓練數據轉換爲DataFrame
#這裏的參數data可以是分割之後的訓練集或者測試集
train_data = pd.DataFrame(train_set)
#快速查看對數據的描述
train_data.info()
#查看各個類別的比例
print(train_data[784].value_counts() / len(train_data))

下面各圖分別是:原始數據10個類別所佔的比例、隨機採樣訓練集中10個類別所佔比例以及分層採樣訓練集中10個類別所佔的比例(當然也可以對測試集進行統計)。

統計對比

通過上面的分析可以看出,分層採樣出的10個類別所佔的比例和原數據中的10個類別所佔的比例很接近。

2.驗證集

前面說到我們將數據集劃分爲訓練集和測試集,我們讓模型在訓練集上進行訓練,然後在測試集上來近似模型的泛化能力。我們如果想要挑選不同的模型的話,可以讓兩個模型分別在訓練集上訓練,然後將兩個訓練好的模型分別在測試集上進行測試,由於我們把測試集上的誤差近似近似爲泛化誤差,所以我們自然可以選擇在測試集上誤差小的模型作爲最終我們要選擇的泛化能力強的模型。

但是我們要做的不僅是不同的模型與模型之間的對比,很多時候我們需要對模型本身進行選擇,假如我們有兩個模型,線性模型和神經網絡模型,我們知道神經網絡的泛化能力要比線性模型要強,我們選擇了神經網絡模型,但是神經網絡中還有很多的需要人工進行選擇的參數,比如神經網絡的層數和每層神經網絡的神經元個數以及正則化的一些參數等等,我們將這些參數稱爲超參數。這些參數不同選擇對模型最終的效果也很重要,我們在開發模型的時候總是需要調節這些超參數。

現在我們需要調節這些超參數來使得模型泛化能力最強。我們使用測試集來作爲泛化誤差估計,而我們最終的目的就是選擇泛化能力強的模型,那麼我們可以直接通過模型在測試集上的誤差來調節這些參數不就可以了。可能模型在測試集上的誤差爲0,但是你拿着這樣的模型去部署到真實場景中去使用的話,效果可能會非常差。

這一現象叫做信息泄露。我們使用測試集作爲泛化誤差的近似,所以不到最後是不能將測試集的信息泄露出去的,就好比考試一樣,我們平時做的題相當於訓練集,測試集相當於最終的考試,我們通過最終的考試來檢驗我們最終的學習能力,將測試集信息泄露出去,相當於學生提前知道了考試題目,那最後再考這些提前知道的考試題目,當然代表不了什麼,你在最後的考試中得再高的分數,也不能代表你學習能力強。而如果通過測試集來調節模型,相當於不僅知道了考試的題目,學生還都學會怎麼做這些題了(因爲我們肯定會人爲的讓模型在測試集上的誤差最小,因爲這是你調整超參數的目的),那再拿這些題考試的話,人人都有可能考滿分,但是並沒有起到檢測學生學習能力的作用。原來我們通過測試集來近似泛化誤差,也就是通過考試來檢驗學生的學習能力,但是由於信息泄露,此時的測試集即考試無任何意義,現實中可能學生的能力很差。所以,我們在學習的時候,老師會準備一些小測試來幫助我們查缺補漏,這些小測試也就是要說的驗證集。我們通過驗證集來作爲調整模型的依據,這樣不至於將測試集中的信息泄露。

也就是說我們將數據劃分訓練集、驗證集和測試集。在訓練集上訓練模型,在驗證集上評估模型,一旦找到的最佳的參數,就在測試集上最後測試一次,測試集上的誤差作爲泛化誤差的近似。關於驗證集的劃分可以參考測試集的劃分,其實都是一樣的,這裏不再贅述。

吳恩達老師的視頻中,如果當數據量不是很大的時候(萬級別以下)的時候將訓練集、驗證集以及測試集劃分爲6:2:2;若是數據很大,可以將訓練集、驗證集、測試集比例調整爲98:1:1;但是當可用的數據很少的情況下也可以使用一些高級的方法,比如留出方,K折交叉驗證等。

 

參考:
1.《Hands-On Machine Learning with Scikit-Learn and TensorFlow》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章