2. 模型評估的方法
2.1 泛化能力
- 泛化能力:指模型對未知的、新鮮的資料的預測能力,通常是根據測試誤差來衡量模型的泛化能力,測試誤差越小,模型能力越強;
- 統計理論表明:如果訓練集和測試集中的樣本都是獨立同分布産生的,則有 模型的訓練誤差的期望等于模型的測試誤差的期望 。
- 機器學習的“沒有免費的午餐定理”表明:在所有可能的資料生成分布上,沒有一個機器學習算法總是比其他的要好。
- 該結論僅在考慮所有可能的資料分布時才成立。
- 現實中特定任務的資料分布往往滿足某類假設,進而可以設計在這類分布上效果更好的學習算法。
- 這意味着機器學習并不需要尋找一個通用的學習算法,而是尋找一個在關心的資料分布上效果最好的算法。
- 正則化是對學習算法做的一個修改,這種修改趨向于降低泛化誤差(而不是降低訓練誤差)。
- 正則化是機器學習領域的中心問題之一。
- 沒有免費的午餐定理說明了沒有最優的學習算法,是以也沒有最優的正則化形式。
2.2 泛化能力的評估
常用的對模型泛化能力的評估方法有以下幾種,主要差別就是如何劃分測試集。
- 留出法(Holdout)
-
交叉驗證(Cross Validation)k-fold
- 留一法(Leave One Out, LOO)
- 自助法(bootstrapping)
2.2.1 留出法(Holdout)
留出法是最簡單也是最直接的驗證方法,它就是将資料集随機劃分為兩個互斥的集合,即訓練集和測試集,比如按照 7:3 的比例劃分,70% 的資料作為訓練集,30% 的資料作為測試集。也可以劃分為三個互斥的集合,此時就增加一個驗證集,用于調試參數和選擇模型。
直接采用
sklearn
庫的
train_test_split
函數即可實作,一個簡單的示例代碼如下,這裡簡單調用
knn
算法,采用
Iris
資料集。
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
# 加載 Iris 資料集
dataset = load_iris()
# 劃分訓練集和測試集
(trainX, testX, trainY, testY) = train_test_split(dataset.data, dataset.target, random_state=3, test_size=0.3)
# 建立模型
knn = KNeighborsClassifier()
# 訓練模型
knn.fit(trainX, trainY)
# 将準确率列印
print('hold_out, score:', knn.score(testX, testY))
留出法的使用需要注意:
-
資料集的劃分要盡可能保持資料分布的一緻性,避免因為資料劃分過程引入額外的偏差而對最終結果産生影響。比如訓練、驗證和測試集的類别比例差别很大,則誤差估計将由于三個集合資料分布的差異而産生偏差。
是以,分類任務中必須保持每個集合中的類别比例相似。從采樣的角度看資料集的劃分過程,這種保留類别比例的采樣方式稱為“分層采樣”。
- 即便确定了訓練、驗證、測試集的比例,還是有多種劃分方式,比如排序後劃分、随機劃分等等,這些不同的劃分方式導緻單次留出法得到的估計結果往往不夠穩定可靠。是以,使用留出法的時候,往往采用若幹次随機劃分、重複進行實驗後,取平均值作為最終評估結果。
分層采樣的簡單代碼實作如下所示,主要是調用了
sklearn.model_selection
中的
StratifiedKFold
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
def StratifiedKFold_method(n_splits=3):
'''
分層采樣
:return:
'''
# 加載 Iris 資料集
dataset = load_iris()
data = dataset.data
label = dataset.target
# 建立模型
knn = KNeighborsClassifier()
print('use StratifiedKFold')
skfolds = StratifiedKFold(n_splits=n_splits, random_state=42)
scores = 0.
for train_index, test_index in skfolds.split(data, label):
clone_clf = clone(knn)
X_train_folds = data[train_index]
y_train_folds = (label[train_index])
X_test_fold = data[test_index]
y_test_fold = (label[test_index])
clone_clf.fit(X_train_folds, y_train_folds)
y_pred = clone_clf.predict(X_test_fold)
n_correct = sum(y_pred == y_test_fold)
print(n_correct / len(y_pred))
scores += n_correct / len(y_pred)
print('mean scores:', scores / n_splits)
留出法也存在以下的缺點:
- 在驗證集或者測試集上的評估結果和劃分方式有關系,這也就是為什麼需要多次實驗,取平均值;
- 我們希望評估的是在原始資料集上訓練得到的模型的能力,但留出法在劃分兩個或者三個集合後,訓練模型僅使用了原始資料集的一部分,這會降低評估結果的保真性。但這個問題沒有完美的解決方法,常見做法是将大約
的樣本作為訓練集,剩餘的作為驗證集和測試集。2/3 ~ 4/5
2.2.2 k-fold
k-fold
k-fold
交叉驗證 的工作流程:
- 将原始資料集劃分為
個大小相等且互斥的子集;k
- 選擇
個子集作為訓練集,剩餘作為驗證集進行模型的訓練和評估,重複k-1
次(每次采用不同子集作為驗證集);k
- 将
次實驗評估名額的平均值作為最終的評估結果。k
通常,
k
取 10。
但和留出法類似,同樣存在多種劃分
k
個子集的方法,是以依然需要随時使用不同方式劃分
p
次,每次得到
k
個子集。
同樣,采用
sklearn.cross_validation
的
cross_val_score
庫可以快速實作
k-fold
交叉驗證法,示例如下:
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cross_validation import cross_val_score
# 加載 Iris 資料集
dataset = load_iris()
data = dataset.data
label = dataset.target
# 建立模型
knn = KNeighborsClassifier()
# 使用K折交叉驗證子產品
scores = cross_val_score(knn, data, label, cv=10, scoring='accuracy')
# 将每次的預測準确率列印出
print(scores)
# 将預測準确平均率列印出
print(scores.mean())
2.2.3 留一法
留一法是
k-fold
交叉驗證的一個特例情況,即讓
k=N
, 其中
N
是原始資料集的樣本數量,這樣每個子集就隻有一個樣本,這就是留一法。
留一法的優點就是訓練資料更接近原始資料集了,僅僅相差一個樣本而已,通過這種方法訓練的模型,幾乎可以認為就是在原始資料集上訓練得到的模型 。
但缺點也比較明顯,計算速度會大大降低,特别是原始資料集非常大的時候,訓練
N
個模型的計算量和計算時間都很大,是以一般實際應用中很少采用這種方法。
2.2.4 自助法
在留出法和
k-fold
交叉驗證法中,由于保留了一部分樣本用于測試,是以實際訓練模型使用的訓練集比初始資料集小,這必然會引入一些因為訓練樣本規模不同而導緻的估計偏差。
留一法受訓練樣本規模變化的影響較小,但是計算複雜度太高。
自助法是一個以自助采樣法(
bootstrap sampling
)為基礎的比較好的解決方案。同時,它也是随機森林算法中用到的方法。
它的做法就是對樣本數量為
N
的資料集進行
N
次有放回的随機采樣,得到一個大小是
N
的訓練集。
在這個過程中将會有一部分資料是沒有被采樣得到的,一個樣本始終沒有被采樣出來的機率是 ( 1 − 1 N ) N (1-\frac{1}{N})^N (1−N1)N,根據極限可以計算得到:
l i m N → + ∞ ( 1 − 1 N ) N = 1 e ≈ 0.368 lim_{N\rightarrow +\infty}(1-\frac{1}{N})^N=\frac{1}{e}\approx 0.368 limN→+∞(1−N1)N=e1≈0.368
也就是采用自助法,會有 36.8% 的樣本不會出現在訓練集中,使用這部分樣本作為測試集。這種方法也被稱為包外估計。
自助法的優點有:
- 在資料集比較小、難以有效劃分訓練/測試集時很有用:
- 能從初始資料集中産生多個不同的訓練集,這對內建學習等方法而言有很大好處。
但也存在如下缺點:
- 産生的資料集改變了初始資料集的分布,這會引入估計偏差。是以在初始資料量足夠時,留出法和折交叉驗證法更常用。
2.3 訓練集、驗證集、測試集
簡單介紹下訓練集、驗證集和測試集各自的作用:
- 訓練集:主要就是訓練模型,理論上越大越好;
- 驗證集:用于模型調試超參數。通常要求驗證集比較大,避免模型會對驗證集過拟合;
- 測試集:用于評估模型的泛化能力。理論上,測試集越大,評估結果就約精準。另外,測試集必須不包含訓練樣本,否則會影響對模型泛化能力的評估。
驗證集和測試集的對比:
- 測試集通常用于對模型的預測能力進行評估,它是提供模型預測能力的無偏估計;如果不需要對模型預測能力的無偏估計,可以不需要測試集;
- 驗證集主要是用于超參數的選擇。
2.4 劃分資料集的比例選擇方法
那麼一般如何選擇劃分訓練、驗證和測試集的比例呢?通常可以按照如下做法:
- 對于小批量資料,資料的拆分的常見比例為:
- 如果未設定驗證集,則将資料三七分:70% 的資料用作訓練集、30% 的資料用作測試集。
- 如果設定驗證集,則将資料劃分為:60% 的資料用作訓練集、20%的資料用過驗證集、20% 的資料用作測試集。
- 對于大批量資料,驗證集和測試集占總資料的比例會更小。
- 對于百萬級别的資料,其中 1 萬條作為驗證集、1 萬條作為測試集即可。
- 驗證集的目的就是驗證不同的超參數;測試集的目的就是比較不同的模型。
- 一方面它們要足夠大,才足夠評估超參數、模型。
- 另一方面,如果它們太大,則會浪費資料(驗證集和訓練集的資料無法用于訓練)。
- 在
交叉驗證中:先将所有資料拆分成k-fold
份,然後其中k
份作為測試集,其他1
份作為訓練集。k-1
- 這裡并沒有驗證集來做超參數的選擇。所有測試集的測試誤差的均值作為模型的預測能力的一個估計。
- 使用
交叉的原因是:樣本集太小。如果選擇一部分資料來訓練,則有兩個問題:k-fold
- 訓練資料的分布可能與真實的分布有偏離。
交叉讓所有的資料參與訓練,會使得這種偏離得到一定程度的修正。k-fold
- 訓練資料太少,容易陷入過拟合。
-fold 交叉讓所有資料參與訓練,會一定程度上緩解過拟合。k
- 訓練資料的分布可能與真實的分布有偏離。
2.5 分布不比對
深度學習時代,經常會發生:訓練集和驗證集、測試集的資料分布不同。
如:訓練集的資料可能是從網上下載下傳的高清圖檔,測試集的資料可能是使用者上傳的、低像素的手機照片。
- 必須保證驗證集、測試集的分布一緻,它們都要很好的代表你的真實應用場景中的資料分布。
- 訓練資料可以與真實應用場景中的資料分布不一緻,因為最終關心的是在模型真實應用場景中的表現。
如果發生了資料不比對問題,則可以想辦法讓訓練集的分布更接近驗證集。
- 一種做法是:收集更多的、分布接近驗證集的資料作為訓練集合。
- 另一種做法是:人工合成訓練資料,使得它更接近驗證集。該政策有一個潛在問題:你可能隻是模拟了全部資料空間中的一小部分。導緻你的模型對這一小部分過拟合。
當訓練集和驗證集、測試集的資料分布不同時,有以下經驗原則:
-
確定驗證集和測試集的資料來自同一分布。
因為需要使用驗證集來優化超參數,而優化的最終目标是希望模型在測試集上表現更好。
- 確定驗證集和測試集能夠反映未來得到的資料,或者最關注的資料。
- 確定資料被随機配置設定到驗證集和測試集上。
當訓練集和驗證集、測試集的資料分布不同時,分析偏差和方差的方式有所不同。
- 如果訓練集和驗證集的分布一緻,那麼當訓練誤差和驗證誤差相差較大時,我們認為存在很大的方差問題。
- 如果訓練集和驗證集的分布不一緻,那麼當訓練誤差和驗證誤差相差較大時,有兩種原因:
- 第一個原因:模型隻見過訓練集資料,沒有見過驗證集的資料導緻的,是資料不比對的問題。
- 第二個原因:模型本來就存在較大的方差。
、訓練-訓練集
。這時候,訓練-驗證集
訓練-訓練集
是同一分布的。訓練-驗證集
- 模型在
和訓練-訓練集
上的誤差的差距代表了模型的方差。訓練-驗證集
-
和 驗證集上的誤差的差距代表了資料不比對問題的程度。訓練-驗證集
3. 過拟合、欠拟合
機器學習的兩個主要挑戰是過拟合和欠拟合。
過拟合(overfitting):指算法模型在訓練集上的性能非常好,但是泛化能力很差,泛化誤差很大,即在測試集上的效果卻很糟糕的情況。
- 過拟合的原因:将訓練樣本本身的一些特點當作了所有潛在樣本都具有的一般性質,這會造成泛化能力下降;另一個原因是模型可能學到訓練集中的噪聲,并基于噪聲進行了預測;
- 過拟合無法避免,隻能緩解。因為機器學習的問題通常是
難甚至更難的,而有效的學習算法必然是在多項式時間内運作完成。如果可以避免過拟合,這就意味着構造性的證明了NP
。P=NP
欠拟合(underfitting):模型的性能非常差,在訓練資料和測試資料上的性能都不好,訓練誤差和泛化誤差都很大。其原因就是模型的學習能力比較差。
一般可以通過挑戰模型的容量來緩解過拟合和欠拟合問題。模型的容量是指其拟合各種函數的能力。
- 容量低的模型容易發生欠拟合,模型拟合能力太弱。
- 容量高的模型容易發生過拟合,模型拟合能力太強。
一般解決過拟合的方法有:
- 簡化模型,這包括了采用簡單點的模型、減少特征數量,比如神經網絡中減少網絡層數或者權重參數,決策樹模型中降低樹的深度、采用剪枝等;
- 增加訓練資料,采用資料增強的方法,比如人工合成訓練資料等;
- 早停,當驗證集上的誤差沒有進一步改善,訓練提前終止;
- 正則化,常用 L1 或者 L2 正則化。
- 內建學習方法,訓練多個模型,并以每個模型的平均輸出作為結果,降低單一模型的過拟合風險,常用方法有
bagging
boosting
(深度學習中的方法)等;dropout
- 噪聲注入:包括輸入噪聲注入、輸出噪聲注入、權重噪聲注入。将噪聲分别注入到輸入/輸出/權重參數中,雖然噪聲可能是模型過拟合的一個原因,但第一可以通過交叉驗證來避免;第二就是沒有噪聲的完美資料也是很有可能發生過拟合;第三可以選擇在特征、權值參數加入噪聲,而非直接在資料加入噪聲。
解決欠拟合的方法有:
- 選擇一個更強大的模型,帶有更多參數
- 用更好的特征訓練學習算法(特征工程)
- 減小對模型的限制(比如,減小正則化超參數)
4. 超參數調優
超參數調優是一件非常頭疼的事情,很多時候都需要一些先驗知識來選擇合理的參數值,但如果沒有這部分先驗知識,要找到最優的參數值是很困難,非常耗費時間和精力。但超參數調優确實又可以讓模型性能變得更加的好。
在選擇超參數調優算法前,需要明确以下幾個要素:
- 目标函數。算法需要最大化/最小化的目标;
- 搜尋範圍。一般通過上下限來确定;
- 算法的其他參數,比如搜尋步長。
4.1 搜尋政策
常用的幾種超參數搜尋政策如下:
- 手動搜尋:需要較好的先驗知識經驗
- 網格搜尋:超參數的資料相對較少的時候,這個方法比較實用
- 随機搜尋:通常推薦這種方式
- 貝葉斯優化算法:基于模型的搜尋方法,利用了曆史搜尋結果
4.1.1 手動搜尋
- 手動選擇超參數需要了解超參數做了些什麼,以及機器學習模型如何才能取得良好的泛化。
- 手動搜尋超參數的任務是:在給定運作時間和記憶體預算範圍的條件下,最小化泛化誤差。
- 手動調整超參數時不要忘記最終目标:提升測試集性能。
- 加入正則化隻是實作這個目标的一種方法。
-
如果訓練誤差很低,也可以通過收集更多的訓練資料來減少泛化誤差。
如果訓練誤差太大,則收集更多的訓練資料就沒有意義。
-
實踐中的一種暴力方法是:不斷提高模型容量和訓練集的大小。
這種方法增加了計算代價,隻有在擁有充足的計算資源時才可行。
4.1.2 網格搜尋
網格搜尋可能是最簡單也是應用最廣泛的超參數搜尋算法了。它的幾種做法如下:
- 采用較大的搜尋範圍和較小的搜尋步長,很大機率會搜尋到全局最優值,但十分耗費計算資源和時間,特别是超參數比較多的時候;
- 先采用較大搜尋範圍和較大步長,尋找全局最優的可能位置,然後逐漸縮小搜尋範圍和步長,來确定更精确的最優值。可以降低所需要的計算時間和計算量,但由于目标函數一般都是非凸的,可能會錯過全局最優值。
網格搜尋也可以借助
sklearn
實作,簡單的示例代碼如下:
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
param_grid = [
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error')
grid_search.fit(data, labels)
4.1.3 随機搜尋
随機搜尋是一種可以替代網格搜尋的方法,它程式設計簡單、使用友善、能更快收斂到超參數的良好取值。
- 首先為每個超參數定義一個邊緣分布,如伯努利分布(對應着二進制超參數)或者對數尺度上的均勻分布(對應着正實值超參數)。
- 然後假設超參數之間互相獨立,從各分布中抽樣出一組超參數。
- 使用這組超參數訓練模型。
- 經過多次抽樣 -> 訓練過程,挑選驗證集誤差最小的超參數作為最好的超參數。
随機搜尋的優點如下:
- 不需要離散化超參數的值,也不需要限定超參數的取值範圍。這允許我們在一個更大的集合上進行搜尋。
- 當某些超參數對于性能沒有顯著影響時,随機搜尋相比于網格搜尋指數級地高效,它能更快的減小驗證集誤差。
随機搜尋比網格搜尋更快的找到良好超參數的原因是:沒有浪費的實驗。
- 在網格搜尋中,兩次實驗之間隻會改變一個超參數 (假設為
)的值,而其他超參數的值保持不變。如果這個超參數m
的值對于驗證集誤差沒有明顯差別,那麼網格搜尋相當于進行了兩個重複的實驗。m
- 在随機搜尋中,兩次實驗之間,所有的超參數值都不會相等,因為每個超參數的值都是從它們的分布函數中随機采樣而來。是以不大可能會出現兩個重複的實驗。
- 如果
超參數與泛化誤差無關,那麼不同的m
值:m
- 在網格搜尋中,不同
值、相同的其他超參數值,會導緻大量的重複實驗。m
- 在随機搜尋中,其他超參數值每次也都不同,是以不大可能出現兩個重複的實驗(除非所有的超參數都與泛化誤差無關)。
- 在網格搜尋中,不同
随機搜尋可以采用
sklearn.model_selection
RandomizedSearchCV
方法。
4.1.4 貝葉斯優化方法
貝葉斯優化方法是基于模型的參數搜尋算法的一種比較常見的算法。它相比于前面的網格搜尋和随機搜尋,最大的不同就是利用曆史的搜尋結果進行優化搜尋。主要是由四部分組成的:
- 目标函數。大部分情況是模型驗證集上的損失;
- 搜尋空間。各類待搜尋的超參數;
- 優化政策。建立的機率模型和選擇超參數的方式;
- 曆史的搜尋結果。
貝葉斯優化算法的步驟如下:
- 根據先驗分布,假設一個搜尋函數;
- 然後,每一次采用新的采樣點來測試目标函數時,利用這個資訊更新目标函數的先驗分布;
- 最後,算法測試由後驗分布給出的全局最優最可能出現的位置的點。
需要特别注意的是,貝葉斯優化算法容易陷入局部最優值:它在找到一個局部最優值後,會不斷在該區域進行采樣。
是以,貝葉斯優化算法會在探索和利用之間找到一個平衡點,探索是在還未取樣的區域擷取采樣點,利用則是根據後驗分布在最可能出現全局最優的區域進行采樣。
4.2 調整原則
- 通常先對超參數進行粗調,然後在粗調中表現良好的超參數區域進行精調。
- 超參數随機搜尋,并不意味着是在有效範圍内随機均勻取值。需要選擇合适的縮放來進行随機選取。
- 通常情況下,建議至少每隔幾個月重新評估或者修改超參數。因為随着時間的變化,真實場景的資料會逐漸發生改變:
- 可能是由于使用者的行為、偏好發生了改變。
- 可能是采樣的方式發生了改變。
- 也可能僅僅是由于資料中心更新了伺服器。
- 有兩種超參數調整政策:
-
如果資料足夠大且沒有足夠的計算資源,此時隻能一次完成一個試驗。
則可以每天觀察模型的表現,實時的、動态的調整超參數。
- 如果資料不大,有足夠的計算資源可以同一時間完成大量的試驗,則可以設定多組超參數設定,然後選擇其中表現最好的那個。
-
小結
關于模型評估方面的内容就介紹這麼多,文章有些長,而且内容也比較多。
關于如何建構一個機器學習項目的内容,基本到本文就介紹完畢了,從開始的評估問題,擷取資料,到資料預處理、特征工程,然後就是各種常見機器學習算法的評估,最後就是模型評估部分的内容了。
當然了,本系列的文章還是偏向于理論,代碼比較少,主要也是整理和總結書本以及網上文章的知識點。
是以下一篇文章會是介紹一篇手把手教你運用機器學習算法來做分類的文章,來自國外一個大神的部落格文章,主要是面向機器學習的初學者。