PU Learning簡介:對無標籤數據進行半監督分類
- 2020 年 3 月 28 日
- 筆記
當只有幾個正樣本,你如何分類無標籤數據
假設您有一個交易業務數據集。有些交易被標記為欺詐,其餘交易被標記為真實交易,因此您需要設計一個模型來區分欺詐交易和真實交易。 假設您有足夠的數據和良好的特徵,這似乎是一項簡單的分類任務。 但是,假設數據集中只有15%的數據被標記,並且標記的樣本僅屬於一類,即訓練集15%的樣本標記為真實交易,而其餘樣本未標記,可能是真實交易樣本,也可能是欺詐樣本。您將如何對其進行分類? 樣本不均衡問題是否使這項任務變成了無監督學習問題? 好吧,不一定。
此問題通常被稱為PU(正樣本和未標記)分類問題,首先要將該問題與兩個相似且常見的“標籤問題”相區別,這兩個問題使許多分類任務複雜化。第一個也是最常見的標籤問題是小訓練集問題。當您有大量數據但實際上只有一小部分被標記時,就會出現這種情況。這個問題有很多種類和許多特定的訓練方法。另一個常見的標籤問題(通常與PU問題混為一談)是,訓練的數據集是被完全標記的但只有一個類別。例如,假設我們擁有的只是一個非欺詐性交易的數據集,並且我們需要使用該數據集來訓練一個模型,以區分非欺詐性交易和欺詐性交易。這也是一個常見問題,通常被視為無監督的離群點檢測問題,在機器學習領域中也有很多工具專門用於處理這些情況(OneClassSVM可能是最著名的)。
相比之下,PU分類問題涉及的訓練集,其中僅部分數據被標記為正,而其餘數據未標記,可能為正或負。 例如,假設您在銀行工作,可以獲得很多交易數據,但只能確認其中一部分是100%真實的。 我將要舉的例子將與偽鈔相關。這個例子包含一個1200張鈔票的數據集,其中大部分未標記,只有一部分被確認為真鈔。 儘管PU問題也很普遍,但是與前面提到的兩個分類問題相比,這個問題被討論的次數通常要少得多,而且很少有實際的示例或庫可供使用。
這篇文章的目的是提出一種可行的辦法來解決PU問題,這個方法我最近在一個分類項目中使用過。 它基於Charles Elkan和Keith Noto的論文《Learning classifiers from only positive and unlabeled data》(2008年),以及Alexandre Drouin撰寫的一些程式碼。 儘管在科學出版物中有更多的PU學習方法(我打算在以後的文章中討論另一種頗受歡迎的方法),但是Elkan和Noto(E&N)的方法非常簡單,並且可以在Python中輕鬆實現。
一點點理論
E&N方法本質上認為,在給定一個具有正樣本和未標記樣本的數據集的情況下,某個樣本為正的概率[P(y = 1 | x)]等於一個樣本被標記的概率[P(s = 1 | x)]除以一個正樣本被標記的概率[P(s = 1 | y = 1)]。
如果這個說法是正確的(我並不會去證明或反駁它-您可以閱讀論文本身的證明並驗證程式碼),那麼實現起來似乎相對容易。 之所以這樣,是因為儘管我們沒有足夠的標記數據來訓練分類器來告訴我們樣本是正還是負,但在PU問題中,我們有足夠的標記數據來告訴我們一個正樣本是否可能被標記。根據E&N的方法,這足以估算一個樣本是否是正樣本
更正式地講,給定一個未標記的數據集,其中只有一組樣本被標記為正樣本。幸運的是,如果我們可以估計P(s = 1 | x)/ P(s = 1 | y = 1),那麼就可以根據以下步驟使用任何基於sklearn的分類器進行估算:
(1)將分類器使用在包含標籤和無標籤樣本的數據集上,同時使用已標記的指示器作為目標y,以這種方式擬合分類器對其進行訓練,以預測給定樣本x被標記的概率P(s = 1 | x)。
(2)使用分類器來預測數據集中已知正樣本被標記的概率,使用預測的結果表示對正樣本被標記的概率— P(s = 1 | y = 1 | x)
計算這些預測概率的均值,得到P(s=1|y=1).
在估計了P(s = 1 | y = 1)之後,為了根據E&N方法預測數據點k為正樣本的概率,我們要做的就是估計P(s = 1 | k)或K被標記的概率,這正是分類器(1)所做的。
(3)使用我們訓練的分類器(1)來估計K被標記的概率或者P(s=1|k)
(4)一旦我們估計了P(s = 1 | k),我們就可以通過將k除以在步驟(2)中估計的P(s = 1 | y = 1)來對k進行分類,然後獲得它屬於這兩個類的實際概率。
現在編寫程式碼並進行測試
上述1-4可按如下方式實施:
# prepare data x_data = the training set y_data = target var (1for the positives and not-1for the rest) # fit the classifier and estimate P(s=1|y=1) classifier, ps1y1 = fit_PU_estimator(x_data, y_data, 0.2, Estimator()) # estimate the prob that x_data is labeled P(s=1|X) predicted_s = classifier.predict_proba(x_data) # estimate the actual probabilities that X is positive# by calculating P(s=1|X) / P(s=1|y=1) predicted_y = estimated_s / ps1y1
讓我們從這裡的主要方法開始:fit_PU_estimator()方法。
fit_PU_estimator()方法完成了2個主要任務:它適合您在正樣本和未標記樣本的數據集中選擇合適的分類器,然後估計正樣本被標記的概率。相應地,它返回一個擬合的分類器(已學會估計給定樣本被標記的概率)和估計概率P(s = 1 | y = 1)。 之後,我們要做的就是找到P(s = 1 | x)或x被標記的概率。因為分類器被這樣訓練過,所以我們只需要調用其predict_proba()方法即可。最後,為了對樣本x進行實際分類,我們只需要將結果除以已經得到的P(s = 1 | y = 1)。
用程式碼表示為:
pu_estimator, probs1y1 = fit_PU_estimator( x_train, y_train, 0.2, xgb.XGBClassifier()) predicted_s = pu_estimator.predict_proba(x_train) predicted_s = predicted_s[:,1] predicted_y = predicted_s / probs1y1
fit_PU_estimator()方法本身的實現是非常簡單的
deffit_PU_estimator(X,y, hold_out_ratio, estimator):# The training set will be divided into a fitting-set that will be used # to fit the estimator in order to estimate P(s=1|X) and a held-out set of positive samples# that will be used to estimate P(s=1|y=1)# --------# find the indices of the positive/labeled elementsassert (type(y) == np.ndarray), "Must pass np.ndarray rather than list as y" positives = np.where(y == 1.)[0] # hold_out_size = the *number* of positives/labeled samples # that we will use later to estimate P(s=1|y=1) hold_out_size = int(np.ceil(len(positives) * hold_out_ratio)) np.random.shuffle(positives) # hold_out = the *indices* of the positive elements # that we will later use to estimate P(s=1|y=1) hold_out = positives[:hold_out_size] # the actual positive *elements* that we will keep aside X_hold_out = X[hold_out] # remove the held out elements from X and y X = np.delete(X, hold_out,0) y = np.delete(y, hold_out) # We fit the estimator on the unlabeled samples + (part of the) positive and labeled ones.# In order to estimate P(s=1|X) or what is the probablity that an element is *labeled* estimator.fit(X, y) # We then use the estimator for prediction of the positive held-out set # in order to estimate P(s=1|y=1) hold_out_predictions = estimator.predict_proba(X_hold_out) #take the probability that it is 1 hold_out_predictions = hold_out_predictions[:,1] # save the mean probability c = np.mean(hold_out_predictions) return estimator, c defpredict_PU_prob(X, estimator, prob_s1y1): prob_pred = estimator.predict_proba(X) prob_pred = prob_pred[:,1] return prob_pred / prob_s1y1
為了對此進行測試,我使用了鈔票數據集,該數據集基於從真實和偽造鈔票的影像中提取的4個數據點。 我首先在標記的數據集上使用分類器以設置基準線,然後刪除75%的樣本的標籤以測試其在P&U數據集上的表現。如輸出所示,確實該數據集並不是最難分類的數據集,但是您可以看到,儘管PU分類器僅了解約153個正樣本,而其餘所有1219均未標記,但與全標籤分類器相比,它的表現相當出色 。 但是,它確實損失了大約17%的召回率,因此損失了很多正樣本。但是,我相信與其他方案相比,這個結果是令人相當滿意的。
===>> load data set <<===data size: (1372, 5)Target variable (fraud or not): 07621610===>> create baseline classification results <<===Classification results:f1: 99.57% roc: 99.57% recall: 99.15% precision: 100.00%===>> classify on all the data set <<===Target variable (labeled or not): -112191153Classification results:f1: 90.24% roc: 91.11% recall: 82.62%
幾個要點
首先,這種方法的性能很大程度上取決於數據集的大小。 在此示例中,我使用了大約150個正樣本和大約1200個未標記的樣本。 這遠不是該方法的理想數據集。 例如,如果我們只有100個樣本,則分類器的效果會非常差。 其次,如隨附的筆記所示,有一些變數需要調整(例如要設置的樣本大小,用於分類的概率閾值等),但是最重要的可能是選擇的分類器及其參數。 我之所以選擇使用XGBoost,是因為它在特徵少的小型數據集上的性能相對較好,但是需要注意的是,它並非在每種情況下都表現得很好,並且測試正確的分類器也很重要。
作者:Alon Agmon
Deephub翻譯組:gkkkkkk