1. 程式人生 > 其它 >【機器學習】偽標籤(Pseudo-Labelling)的介紹:一種半監督機器學習技術

【機器學習】偽標籤(Pseudo-Labelling)的介紹:一種半監督機器學習技術

我們在解決監督機器學習的問題上取得了巨大的進步。這也意味著我們需要大量的資料來構建我們的影象分類器。但是,這並不是人類思維的學習方式。一個人的大腦不需要上百萬個數據來進行訓練,需要通過多次迭代來完成相同的影象來理解一個主題。它所需要的只是在基礎模式上用幾個指導點訓練自己。顯然,我們在當前的機器學習方法中缺少一些東西。我們能否可以建立一個系統,能夠要求最低限度的監督,並且能夠自己掌握大部分的任務。

本文將介紹一種稱為偽標籤(Pseudo-Labelling)的技術。我會給出一個直觀的解釋,說明偽標籤是什麼,然後提供一個實際的實現。

內容

  • 什麼是半監督學習?
  • 不加標籤的資料有何幫助?
  • 介紹偽標籤
  • 實現半監督學習
  • 取樣率的依賴
  • 半監督學習的應用

什麼是半監督學習?

比方說,我們有一個簡單的影象分類問題。訓練資料由兩個被標記的影象Eclipse(日食)和Non-eclipse(非日食)組成,如下所示。

我們需要從非日食影象中對日食的影象進行分類。但是,問題是我們需要在僅僅兩張圖片的訓練集上建立我們的模型。

因此,為了應用任何監督學習演算法,我們需要更多的資料來構建一個魯棒性的模型。為了解決這個問題,我們找到了一個簡單的解決方案,我們從網上下載一些圖片來增加我們的訓練資料。

但是,對於監督的方法,我們也需要這些影象的標籤。因此,我們手動將每個影象分類為如下所示的類別。

在對這些資料進行監督的演算法之後,我們的模型肯定會比在訓練資料中包含兩個影象的模型表現得更突出。但是這種方法只適用於解決較小目的的問題,因為對大型資料集的註釋可能非常困難且昂貴。

因此,我們定義了一種不同型別的學習,即半監督學習,即使用標籤資料(受監督的學習)和不加標籤的資料(無監督的學習)。

因此,讓我們瞭解不加標籤的資料如何有助於改進我們的模型。

不加標籤的資料有何幫助?

考慮如下所示的情況:

你只有兩個屬於兩個不同類別的資料點,而所繪製的線是任何受監督模型的決策邊界。現在,假設我們將一些不加標記的資料新增到這個資料中,如下圖所示。

如果我們注意到上面兩個影象之間的差異,你可以說,在添加了不加標籤的資料之後,我們的模型的決策邊界變得更加準確。因此,使用不加標籤資料的好處是:

1.被貼上標籤的資料既昂貴又困難,而沒有標籤的資料則是充足而廉價的。

2.它通過更精確的決策邊界來改進模型的魯棒性。

現在,我們對半監督學習有了一個基本的瞭解。有多種不同的技術在應用著半監督學習,在本文中,我們將嘗試理解一種稱為偽標籤的技術。

介紹偽標籤

在這種技術中,我們不需要手動標記不加標籤的資料,而是根據標籤的資料給出近似的標籤。讓我們通過分解如下圖所示的步驟來使它更容易理解。

第一步:使用標籤資料訓練模型

第二步:使用訓練的模型為不加標籤的資料預測標籤

第三步:同時使用pseudo和標籤資料集重新訓練模型

在第三步中訓練的最終模型用於對測試資料的最終預測。

實現半監督學習

現在,我們將使用來自AV資料處理平臺的大市場銷售(Big Mart Sales)問題。因此,讓我們從下載資料部分中的訓練和測試檔案開始。

那麼,讓我們從匯入基本庫開始。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.preprocessing import LabelEncoder

現在,看一下我們下載的訓練和測試檔案,並進行一些基本的預處理,以形成模型。

train = pd.read_csv('/Users/shubhamjain/Downloads/AV/Big Mart/train.csv')
test = pd.read_csv('/Users/shubhamjain/Downloads/AV/Big Mart/test.csv')
# preprocessing

### mean imputations 
train['Item_Weight'].fillna((train['Item_Weight'].mean()), inplace=True)
test['Item_Weight'].fillna((test['Item_Weight'].mean()), inplace=True)

### reducing fat content to only two categories 
train['Item_Fat_Content'] = train['Item_Fat_Content'].replace(['low fat','LF'], ['Low Fat','Low Fat']) 
train['Item_Fat_Content'] = train['Item_Fat_Content'].replace(['reg'], ['Regular']) 
test['Item_Fat_Content'] = test['Item_Fat_Content'].replace(['low fat','LF'], ['Low Fat','Low Fat']) 
test['Item_Fat_Content'] = test['Item_Fat_Content'].replace(['reg'], ['Regular'])

## for calculating establishment year
train['Outlet_Establishment_Year'] = 2013 - train['Outlet_Establishment_Year'] 
test['Outlet_Establishment_Year'] = 2013 - test['Outlet_Establishment_Year']

### missing values for size
train['Outlet_Size'].fillna('Small',inplace=True)
test['Outlet_Size'].fillna('Small',inplace=True)

### label encoding cate. var.
col = ['Outlet_Size','Outlet_Location_Type','Outlet_Type','Item_Fat_Content']
test['Item_Outlet_Sales'] = 0
combi = train.append(test)
number = LabelEncoder()
for i in col:
 combi[i] = number.fit_transform(combi[i].astype('str'))
 combi[i] = combi[i].astype('int')
train = combi[:train.shape[0]]
test = combi[train.shape[0]:]
test.drop('Item_Outlet_Sales',axis=1,inplace=True)

## removing id variables 
training = train.drop(['Outlet_Identifier','Item_Type','Item_Identifier'],axis=1)
testing = test.drop(['Outlet_Identifier','Item_Type','Item_Identifier'],axis=1)
y_train = training['Item_Outlet_Sales']
training.drop('Item_Outlet_Sales',axis=1,inplace=True)

features = training.columns
target = 'Item_Outlet_Sales'

X_train, X_test = training, testing

從不同的監督學習演算法開始,讓我們來看看哪一種演算法給了我們最好的結果。

from xgboost import XGBRegressor
from sklearn.linear_model import BayesianRidge, Ridge, ElasticNet
from sklearn.neighbors import KNeighborsRegressor
from sklearn.ensemble import RandomForestRegressor, ExtraTreesRegressor, GradientBoostingRegressor
#from sklearn.neural_network import MLPRegressor

from sklearn.metrics import mean_squared_error
from sklearn.model_selection import cross_val_score
model_factory = [
 RandomForestRegressor(),
 XGBRegressor(nthread=1),
 #MLPRegressor(),
 Ridge(),
 BayesianRidge(),
 ExtraTreesRegressor(),
 ElasticNet(),
 KNeighborsRegressor(),
 GradientBoostingRegressor()
]

for model in model_factory:
 model.seed = 42
 num_folds = 3

scores = cross_val_score(model, X_train, y_train, cv=num_folds, scoring='neg_mean_squared_error')
 score_description = " %0.2f (+/- %0.2f)" % (np.sqrt(scores.mean()*-1), scores.std() * 2)

print('{model:25} CV-5 RMSE: {score}'.format(
 model=model.__class__.__name__,
 score=score_description
 ))

我們可以看到XGB演算法為我們提供了最佳的模型效能。注意,我沒有為了本文的簡單性而調整任何演算法的引數。

現在,讓我們來實現偽標籤,為了這個目的,我將使用測試資料作為不加標籤的資料。

from sklearn.utils import shuffle
 from sklearn.base import BaseEstimator, RegressorMixin

class PseudoLabeler(BaseEstimator, RegressorMixin):
 '''
 Sci-kit learn wrapper for creating pseudo-lebeled estimators.
 '''

def __init__(self, model, unlabled_data, features, target, sample_rate=0.2, seed=42):
 '''
 @sample_rate - percent of samples used as pseudo-labelled data
 from the unlabelled dataset
 '''
 assert sample_rate <= 1.0, 'Sample_rate should be between 0.0 and 1.0.'

self.sample_rate = sample_rate
 self.seed = seed
 self.model = model
 self.model.seed = seed

self.unlabled_data = unlabled_data
 self.features = features
 self.target = target

def get_params(self, deep=True):
 return {
 "sample_rate": self.sample_rate,
 "seed": self.seed,
 "model": self.model,
 "unlabled_data": self.unlabled_data,
 "features": self.features,
 "target": self.target
 }

def set_params(self, **parameters):
 for parameter, value in parameters.items():
 setattr(self, parameter, value)
 return self

def fit(self, X, y):
 '''
 Fit the data using pseudo labeling.
 '''

augemented_train = self.__create_augmented_train(X, y)
 self.model.fit(
 augemented_train[self.features],
 augemented_train[self.target]
 )

return self



def __create_augmented_train(self, X, y):
 '''
 Create and return the augmented_train set that consists
 of pseudo-labeled and labeled data.
 '''
 num_of_samples = int(len(self.unlabled_data) * self.sample_rate)

# Train the model and creat the pseudo-labels
 self.model.fit(X, y)
 pseudo_labels = self.model.predict(self.unlabled_data[self.features])

# Add the pseudo-labels to the test set
 pseudo_data = self.unlabled_data.copy(deep=True)
 pseudo_data[self.target] = pseudo_labels

# Take a subset of the test set with pseudo-labels and append in onto
 # the training set
 sampled_pseudo_data = pseudo_data.sample(n=num_of_samples)
 temp_train = pd.concat([X, y], axis=1)
 augemented_train = pd.concat([sampled_pseudo_data, temp_train])

return shuffle(augemented_train)

def predict(self, X):
 '''
 Returns the predicted values.
 '''
 return self.model.predict(X)

def get_model_name(self):
 return self.model.__class__.__name__

看起來很複雜,但是你不必為此擔心,因為它是我們在上面學習的方法的相同實現。因此,每次需要執行偽標籤時,都要複製相同的程式碼。

現在,讓我們來檢查一下資料集上的偽標籤的結果。

model_factory = [
 XGBRegressor(nthread=1),
 PseudoLabeler(
 XGBRegressor(nthread=1),
 test,
 features,
 target,
 sample_rate=0.3
 ),
]

for model in model_factory:
 model.seed = 42
 num_folds = 8
 
 scores = cross_val_score(model, X_train, y_train, cv=num_folds, scoring='neg_mean_squared_error', n_jobs=8)
 score_description = "MSE: %0.4f (+/- %0.4f)" % (np.sqrt(scores.mean()*-1), scores.std() * 2)

print('{model:25} CV-{num_folds} {score_cv}'.format(
 model=model.__class__.__name__,
 num_folds=num_folds,
 score_cv=score_description
 ))

在這種情況下,我們得到了一個rmse值,這個值比任何受監督的學習演算法都要小。sample_rate(取樣率)是其中一個引數,它表示不加標籤資料的百分比被用作建模目的的偽標籤。

因此,讓我們檢查一下采樣率對偽標籤效能的依賴性。

取樣率的依賴

為了找出樣本率對偽標籤效能的依賴,讓我們在這兩者之間畫一個圖。在這裡,我只使用了兩種演算法來表示對時間約束(time constraint)的依賴,但你也可以嘗試其他演算法。

sample_rates = np.linspace(0, 1, 10)

def pseudo_label_wrapper(model):
 return PseudoLabeler(model, test, features, target)

# List of all models to test
model_factory = [
 RandomForestRegressor(n_jobs=1),
 XGBRegressor(),
]

# Apply the PseudoLabeler class to each model
model_factory = map(pseudo_label_wrapper, model_factory)

# Train each model with different sample rates
results = {}
num_folds = 5

for model in model_factory:
 model_name = model.get_model_name()
 print('%s' % model_name)

results[model_name] = list()
 for sample_rate in sample_rates:
 model.sample_rate = sample_rate
 
 # Calculate the CV-3 R2 score and store it
 scores = cross_val_score(model, X_train, y_train, cv=num_folds, scoring='neg_mean_squared_error', n_jobs=8)
 results[model_name].append(np.sqrt(scores.mean()*-1))
plt.figure(figsize=(16, 18))

i = 1
for model_name, performance in results.items(): 
 plt.subplot(3, 3, i)
 i += 1
 
 plt.plot(sample_rates, performance)
 plt.title(model_name)
 plt.xlabel('sample_rate')
 plt.ylabel('RMSE')


plt.show()

我們可以看到,rmse對於取樣率的特定值來說是最小值,這對於演算法來說是不同的。因此,對取樣率進行調優是很重要的,以便在使用偽標籤時獲得更好的結果。

半監督學習的應用

在過去,半監督學習的應用數量有限,但目前在這一領域仍有很多工作要做。下面列出了一些我感興趣的應用。

1.多模態半監督學習(Multimodal semi-supervised learning)影象分類

一般來說,在影象分類中,目標是對影象進行分類,無論它屬於這個類別還是不屬於類別。本文不僅利用影象進行建模,還利用半監督學習的方法來改進分類器,從而提高分類器的使用效果。

2.檢測人口販賣

人口販賣是最殘忍的犯罪之一,也是法律執行所面臨的挑戰問題之一。半監督學習使用標籤和不加標籤的資料,以產生比一般方法更好的結果。

本文的完整程式碼:https://github.com/shubhamjn1/Pseudo-Labelling—A-Semi-supervised-learning-technique