在數據挖掘領域,我們會根據任務的類型、難度以及實際需求來建立不同的模型,最終建立的模型雖然類型迥異,但建模過程卻遵循著一套科學且成熟的評估和度量思想。模型評估方法主要是為了通過分配訓練集和測試集來估算模型在未知數據集中的泛化能力,以此調整參數或選擇模型;性能度量主要是通過設立的不同指標,從各方面衡量模型的性能,如模型預測的精確性等。大白話來說,就是如何評價機器學習模型好不好。
由於各類方法和指標的概念較多,因此現實中許多人錯誤理解或使用相關概念,從而造成了一些事與願違的案例。因此本文將主要介紹常用的方法和指標,在易混淆的地方加以說明,並介紹相關基於Python的工具。需要注意的是,本文涉及到相關概念的數學表達,原理和性質的推導不講介紹。
1 評估方法1.1留出法一般來說,我們更在乎的是模型在未知數據集上的表現,即模型的泛化能力。為了能夠估計模型的泛化能力,可以將數據集按比例簡單地劃分為訓練集(training set)和驗證集(validation set),其中該比例常取$2:1$或$4:1$,這就是留出法(hold-out)。但需要注意的是,在樣本劃分後需要保證訓練集和測試集的樣本分布和原始數據集的樣本分布一致,因此常基於分層抽樣(stratified sampling)的原則來劃分樣本。
註:測試數據更多地是指模型在實際使用中遇到的數據,為了和模型評估中使用的測試集進行區分,一般會把模型評估用的測試集叫做驗證集(validation set)。
核心代碼如下:
# 適合快速分配訓練集和測試集from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=0)1.2 交叉驗證由於留出法僅對樣本按比例隨機劃分一次,將導致模型評估的結果具有較大隨機性,因此在實際中常使用交叉驗證來評估模型。交叉驗證(cross validation)也稱為k折交叉驗證(k-fold cross validation),即將數據集劃分為k個互斥子集,每次使用k-1個子集的併集作為訓練集,餘下的一個子集作為驗證集,這就構成了k組訓練/驗證集,從而可以進行k次訓練和驗證。最終取k次驗證的均值作為評估結果。其中,k常取5或10,10折交叉驗證的過程如下圖所示。
特別地,令k=n(n為數據集樣本數)的交叉驗證稱為留一法(Leave-One-Out,簡稱LOO),即有多少樣本就進行多少次訓練和驗證,並且每次只留下一個樣本做驗證。該方法的優勢在於無需擔心樣本隨機劃分帶來的誤差,因為每次劃分都是唯一的。一般來說,留一法的評估結果被認為是比較準確的。但是當數據集較大時,使用留一法需要訓練的模型過多,導致計算開銷巨大。
核心代碼如下:
# K折交叉驗證from sklearn.model_selection import KFoldkf = KFold(n_splits=5) #5折交叉for train_index, test_index in kf.split(X): X_train, X_test = X[train_index], X[test_index] y_train, y_test = y[train_index], y[test_index]1.3 自助法自助法(bootstrapping)可以避免因樣本分配規模的不同而造成的偏差,以自助採樣(bootstrap sampling)為基礎,對包含n個樣本的源數據集進行有放回的n次採樣以獲得同等規模的訓練集。在這m次採樣中都不被抽到的概率大約為0.368,也即源數據集中有大約1/3的樣本是訓練集中沒有的。因此,我們可以採用這部分樣本作為驗證集,所得的結果稱為包外估計(out-of-bag estimate)。
該方法適用於數據量小且難以劃分訓練/驗證集的情況。由於自助法能產生多個不同訓練集,所以對集成學習也大有好處。但需要注意的是,自助法改變了數據集的分布,由此引入了一些額外的誤差。因此數據量充足時,建議使用留出法或交叉驗證法。
核心代碼如下:
# mini demoimport numpy as npnp.random.seed(1212)die_vals = np.array([1, 2, 3, 4, 5, 6])# 有放回抽樣np.random.choice(die_vals, size=10)2 度量指標2.1 回歸問題指標2.1.1 RSS殘差平方和(Residual sum of square,RSS)描述了估計量和被估計量之間的差異,是每個樣本點殘差的總和,形式如下:
import numpy as npnp.sum((y_pred - y_true) ** 2)2.1.2 MSE均方誤差(mean-square error,MSE)是RSS的期望,其形式如下:
核心代碼如下:
from sklearn.metrics import mean_squared_errormean_squared_error(y_true, y_pred)2.1.3 RMSE均方根誤差(root-mean-square error,RMSE)是MSE的開放形式,其形式如下:
核心代碼如下:
import numpy as npfrom sklearn.metrics import mean_squared_errornp.sqrt(mean_squared_error(y_true, y_pred))2.1.4 MAE平均絕對誤差(Mean Absolute Error,MAE)對誤差採用絕對值的方式表達,其形式為:
核心代碼如下:
from sklearn.metrics import mean_absolute_errormean_absolute_error(y_true, y_pred)2.1.5 $R^2$確定係數R-Square($R^2$)度量因變量的變異中可由自變量解釋部分所佔的比例,以此來判斷回歸模型的解釋力,形式為:
from sklearn.metrics import r2_scorer2_score(y_true,y_pred)2.2 分類問題指標2.2.1 Confusion matrix混淆矩陣(confusion matrix)常用於監督學習中,矩陣的每一列代表一個類的實例預測,每一行表示一個實際的類的實例,其可以方便地看出機器是否將兩個不同的類混淆了(比如說把一個類錯當成了另一個)。
假設處理一個二分類問題,按照模型預測值和真實值可以把測試樣本劃分為四種情形:真正例(true positive),假正例(false positive),真反例(true negative),假反例(false negative)。混淆矩陣的表示如下:
真實情況預測結果正例反例正例TP(真正例)FN(假反例)反例FP(假正例)TN(真反例)註:positive和negative指的是模型預測的類別,true和false是結合真實情況,對預測類別的一個評判。如真實情況是positive,預測也是positive,那麼則是TP(true positive);如果預測是negative,那麼則是FN(false negative)。
核心代碼如下:
from sklearn.metrics import confusion_matrixconfusion_matrix(y_true, y_pred)2.2.2 Accuracy在分類任務中,通常把分對的樣本數佔樣本總數的比例稱為精度(accuracy),是一個全局的指標,形式如下:
核心代碼如下:
from sklearn.metrics import accuracy_scoreaccuracy_score(y_true, y_pred)2.2.3 Precision查準率,又稱準確率(precision),用于衡量模型避免錯誤的能力,分母是模型預測的正例數目,其形式為:
from sklearn.metrics import precision_scoreprecision_score(y_true, y_pred)2.2.4 Recall查全率,又稱召回率(recall),用于衡量模型避免缺漏的能力,分母是測試樣本真正包含的正例數目,其形式為:
核心代碼如下:
from sklearn.metrics import recall_scorerecall_score(y_true, y_pred)2.2.5 F1 ScoreF1 Score,是查準率和查全率的調和平均,用於綜合考慮這兩個性能度量。
核心代碼如下:
from sklearn.metrics import f1_scoref1_score(y_true, y_pred)2.2.6 ROC很多時候,使用模型對測試樣本進行預測得到的是一個實值或者概率,需要進一步設置閾值(threshold),然後把預測值和閾值進行比較才能獲得最終預測的標記,如邏輯回歸等模型。
ROC,全稱受試者工作特徵(Receiver Operating Characteristic)。先定義兩個重要的計算量:真正例率(True Positive Rate,簡稱TPR)和假正例率(False Positive Rate,簡稱FPR)。
其中,TPR為召回率。在繪製ROC曲線時,縱軸為TPR,橫軸為FPR。首先按預測值對樣本進行排序,然後按序逐個把樣本預測為正例,並計算此時的TPR和FPR,然後在圖上畫出該點,並與前一個點連線。如下圖:
有兩個值得注意的特例:
•經過 (0,1) 點的曲線,這代表所有正例都在反例之前出現(否則會先出現假正例從而無法經過 (0,1) 點),這是一個理想模型,我們可以設置一個閾值,完美地分割開正例和反例。•對角線,這對應於隨機猜測模型,可以理解為真正例和假正例輪換出現,即每預測對一次接下來就預測錯一次,可以看作是隨機猜測的結果。
註:ROC適用於數據類別平衡時的模型評價。當數據類別不平衡時,可以繪製P-R(precision-recall)曲線。
核心代碼如下:
import numpy as npfrom sklearn.metrics import roc_curvefpr, tpr, thresholds = roc_curve(y_true, y_pred, pos_label=2)2.2.7 AUC若一個模型的ROC曲線完全包住了另一個模型的ROC曲線,我們就認為這個模型更優。但是如果兩條曲線發生交叉,要怎麼判斷呢?比較合理的判據是AUC(Area Under ROC Curve),即ROC曲線下的面積,其形式為:
from sklearn.metrics import roc_auc_scoreroc_auc_score(y_true, y_pred)2.2.8 IoU在目標檢測任務中,不僅要識別出圖像中目標對象的類別,還要識別出其在圖像中的位置,並常用矩形框(bounding box)表示。其中,識別類別歸屬於分類問題,可以使用常規分類任務的指標衡量模型的性能,如何高效地評價模型對位置預測的能力呢?答案是IoU(Intersection of Union)(對於IOU有很多改進的算法,本文僅介紹最naive的)。
IoU可以通過判斷預測框(predicted bounding box)和真實框(ground-truth bounding box)的貼合程度來評判模型對位置預測的好壞,如下圖所示,IoU即為兩個矩形框的交集與併集的比值,所以其值範圍為
通過預測框的類別和IoU,可以計算混淆矩陣,以某一個類目標為例(即假設是一個二分類問題),如下圖所示:
混淆矩陣的四個值如下:
•TP(True Positive):表示預測框正確地與標籤框匹配了,兩者間的IoU大於0.5,如上圖中右邊的虛線框。•FP(False Positive):表示將背景預測成了目標物體,如左下方的虛線框,該預測框與真實框的IoU一般不會大於0.5。•FN(False Negative):表示需要模型檢測出的物體,模型沒有檢測出,如左邊的小貓。•TN(True Negative):表示本身是背景,模型也沒有檢測出,這種情況在目標檢測中通常不需要考慮。
核心代碼如下:
def get_area(box): return (box[3] - box[1]) * (box[2] - box[0])def iou(boxA, boxB): left_max = max(boxA[0], boxB[0]) top_max = max(boxA[1], boxB[1]) right_min = min(boxA[2], boxB[2]) bottom_min = min(boxA[3], boxB[3]) inner_area = get_area((left_max, top_max, right_min, bottom_min)) areaA = get_area(boxA) areaB = get_area(boxB) iou = inner_area / (areaA + areaB - inner_area) return iou2.2.9 mAP有了上述混淆矩陣的基礎後,可以使用一個更make sense的指標衡量模型檢測的能力,即mAP(mean Average Precision)。其中,AP指的是一個類別的檢測精度,mAP則是多個類別的平均精度。要計算mAP,首先得計算個類別AP指標,在此之前,得明確兩個概念:
•Dets:預測框中的物體類別、邊框位置的4個預測值、該物體的得分;•GTs:真實框中的物體類別、邊框位置的4個真實值。
AP指標的計算步驟如下圖所示:
得到FP和TP的數量後,可以計算模型的召回率(Recall,R)和準確率(Precision,P),計算過程如下式:
遍歷每一個預測框時,可以生成對應的P與R(注意要累計),這兩個值可以組成一個點(R, P),將所有的點繪製成曲線,即形成了P-R曲線,AP即使P-R曲線下的面積,其形式為
AP代表了曲線面積,綜合考量了不同召回率下的準確率,不會對召回率或準確率其中一個有偏好。此外,每個類別的AP指標都是獨立的,將每個類別的AP取平均後即可得到mAP。
核心代碼如下:
import numpy as npdef calc_ap(prec, rec): mrec = np.array([0, rec, 1]) mpre = np.array([0, prec, 0]) for i in range(mrec.size - 2, -1, -1): mpre[i] = max(mpre[i], mpre[i+1]); idx1 = np.where(mrec[1 : ] != mrec[0 : 2]) idx2 = [x + 1 for x in idx1] ap = sum((mrec.take(idx2) - mrec.take(idx1)) * mpre.take(idx2)) print "ap = " + str(ap) return apmAP = sum(ap)/N2.3 偏差和方差除了估計算法的泛化性能,我們往往還希望知道為什麼有這樣的性能?這時一個有用的工具就是偏差-方差分解(bias-variance decomposition)。
知乎上面有兩個問題都有不錯的答案,不妨先看看:[1] 機器學習中的Bias(偏差),Error(誤差),和Variance(方差)有什麼區別和聯繫?;[2] 偏差和方差有什麼區別?。
對學習算法的期望泛化錯誤率進行拆解,最終會發現能拆解為三個項(需要推導):
•方差:使用同規模的不同訓練集進行訓練時帶來的性能變化,刻畫數據擾動帶來的影響;•偏差:學習算法的期望預測與真實結果的偏離程度,刻畫算法本身的擬合能力;•噪聲:當前任務上任何算法所能達到的期望泛化誤差的下界(即不可能有算法取得更小的誤差),刻畫問題本身的難度;
也即是說,泛化性能是有學習算法的擬合能力,數據的充分性以及問題本身的難度共同決定的。給定一個任務,噪聲是固定的,我們需要做的就是儘量降低偏差和方差。
但是這兩者其實是有衝突的,這稱為偏差-方差窘境(bias-variance dilemma)。給定一個任務,我們可以控制算法的訓練程度(如決策樹的層數)。在訓練程度較低時,擬合能力較差,因此訓練數據的擾動不會讓性能有顯著變化,此時偏差主導泛化錯誤率;在訓練程度較高時,擬合能力很強,以至於訓練數據自身的一些特性都會被擬合,從而產生過擬合問題,訓練數據的輕微擾動都會令模型產生很大的變化,此時方差主導泛化錯誤率。
注意,將泛化性能完美地分解為方差、偏差、噪聲這三項僅在基於均方誤差的回歸任務中得以推導出,分類任務由於損失函數的跳變性導致難以從理論上推導出分解形式,但已經有很多方法可以通過實驗進行估計了。
參考:
1.《機器學習》周志華2.模型評估和選擇3.《深度學習之PyTorch物體檢測實戰》董洪義