偽標籤:教你玩轉無標籤資料的半監督學習方法
對於每個機器學習專案而言,資料是基礎,是不可或缺的一部分。在本文中,作者將會展示一個名為偽標籤的簡單的半監督學習方法,它可以通過使用無標籤資料來提高機器學習模型的效能。
偽標籤
為了訓練機器學習模型,在監督學習中,資料必須是有標籤的。那這是否意味著無標籤的資料對於諸如分類和迴歸之類的監督任務就無用了呢?當然不是! 除了使用額外資料進行資料分析,還可以將無標籤資料和標籤資料結合起來,一同訓練半監督學習模型。
該方法的主旨思想其實很簡單。首先,在標籤資料上訓練模型,然後使用經過訓練的模型來預測無標籤資料的標籤,從而建立偽標籤。此外,將標籤資料和新生成的偽標籤資料結合起來作為新的訓練資料。
這個方法的靈感來自於fast.ai MOOC(原文)。雖然這個方法是在深度學習(線上演算法)的背景下提到的,但是我們在傳統的機器學習模型上進行了嘗試,並得到了細微的提升。
資料預處理與探索
通常在像Kaggle這樣的比賽中,參賽者通常收到的資料是有標籤的資料作為訓練集,無標籤的資料作為測試集。這是一個測試偽標籤的好地方。我們這裡使用的資料集來自Mercedes-Benz Greener Manufacturing competition,該競賽的目標是根據提供的特徵(迴歸)測試一輛汽車的持續時間。與往常一樣,在本筆記本中可以找到附加描述的所有程式碼。
import pandas as pd # Load the data train = pd.read_csv('input/train.csv') test = pd.read_csv('input/test.csv') print(train.shape, test.shape) # (4209, 378) (4209, 377)
從上面我們可以看到,訓練資料並不理想,只有4209組資料,376個特徵。為了改善資料集,我們應該減少特徵資料,儘可能地增加資料量。我在之前的一篇部落格文章中提到過特徵的重要性(特徵壓縮),這個主題暫且略過不談,因為這篇部落格文章的主要重點將是增加帶有偽標籤的資料量。這個資料集可以很好地用於偽標籤,因為小資料中有無標籤的資料比例為1:1。
下表展示的是整個訓練集的子集,特徵x0-x8是分類變數,我們必須把它們轉換成模型可用的數值變數。
這裡使用scikit- learn的LabelEncoder類完成的。
from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split features = train.columns[2:] for column_name in features: label_encoder = LabelEncoder() # Get the column values train_column_values = list(train[column_name].values) test_column_values = list(test[column_name].values) # Fit the label encoder label_encoder.fit(train_column_values + test_column_values) # Transform the feature train[column_name] = label_encoder.transform(train_column_values) test[column_name] = label_encoder.transform(test_column_values)
結果如下:
現在,用於機器學習模型的資料就準備好了。
使用Python和scikit-learn實現偽標籤
我們建立一個函式,包含偽標籤資料和標籤資料的“增強訓練集“。函式的引數包括模型、訓練集、測試集資訊(資料和特徵)和引數sample_rate。Sample_rate允許我們控制混合有真實標籤資料的偽標籤資料的百分比。將sample_rate設定為0.0意味著模型只使用真實標籤的資料,而sample_rate為0.5時意味著模型使用了所有的真實的標籤資料和一半的偽標籤資料。無論哪種情況,模型都將使用所有真實標籤的資料。
def create_augmented_train(X, y, model, test, features, target, sample_rate):
'''
Create and return the augmented_train set that consists
of pseudo-labeled and labeled data.
'''
num_of_samples = int(len(test) * sample_rate)
# Train the model and creat the pseudo-labeles
model.fit(X, y)
pseudo_labeles = model.predict(test[features])
# Add the pseudo-labeles to the test set
augmented_test = test.copy(deep=True)
augmented_test[target] = pseudo_labeles
# Take a subset of the test set with pseudo-labeles and append in onto
# the training set
sampled_test = augmented_test.sample(n=num_of_samples)
temp_train = pd.concat([X, y], axis=1)
augemented_train = pd.concat([sampled_test, temp_train])
# Shuffle the augmented dataset and return it
return shuffle(augemented_train)
此外,我們還需要一個可以接受增強訓練集的方法來訓練模型。這是另一個函式,我們在準備引數之前已經寫過了。這是一個很好的機會,可以建立一個類來增強內聚性,使程式碼更簡潔,並且把方法放入這個類中。我們將要建立的類叫PseudoLabeler.。這個類將採用scikit-learn模型,並利用增強訓練集來訓練它。Scikit-learn允許我們建立自己的迴歸類庫,但是我們必須遵守他們的庫標準。
from sklearn.utils import shuffle
from sklearn.base import BaseEstimator, RegressorMixin
class PseudoLabeler(BaseEstimator, RegressorMixin):
def __init__(self, model, test, features, target, sample_rate=0.2, seed=42):
self.sample_rate = sample_rate
self.seed = seed
self.model = model
self.model.seed = seed
self.test = test
self.features = features
self.target = target
def get_params(self, deep=True):
return {
"sample_rate": self.sample_rate,
"seed": self.seed,
"model": self.model,
"test": self.test,
"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):
if self.sample_rate > 0.0:
augemented_train = self.__create_augmented_train(X, y)
self.model.fit(
augemented_train[self.features],
augemented_train[self.target]
)
else:
self.model.fit(X, y)
return self
def __create_augmented_train(self, X, y):
num_of_samples = int(len(test) * self.sample_rate)
# Train the model and creat the pseudo-labels
self.model.fit(X, y)
pseudo_labels = self.model.predict(self.test[self.features])
# Add the pseudo-labels to the test set
augmented_test = test.copy(deep=True)
augmented_test[self.target] = pseudo_labels
# Take a subset of the test set with pseudo-labels and append in onto
# the training set
sampled_test = augmented_test.sample(n=num_of_samples)
temp_train = pd.concat([X, y], axis=1)
augemented_train = pd.concat([sampled_test, temp_train])
return shuffle(augemented_train)
def predict(self, X):
return self.model.predict(X)
def get_model_name(self):
return self.model.__class__.__name__
除“fit”和“__create_augmented_train”方法以外,scikit-learn還需要一些較小的方法來使用這個類作為迴歸類庫(可從官方文件瞭解更多資訊)。現在我們已經為偽標籤建立了scikit-learn類,我們來舉個例子。
target = 'y'
# Preprocess the data
X_train, X_test = train[features], test[features]
y_train = train[target]
# Create the PseudoLabeler with XGBRegressor as the base regressor
model = PseudoLabeler(
XGBRegressor(nthread=1),
test,
features,
target
)
# Train the model and use it to predict
model.fit(X_train, y_train)
model.predict(X_train)
在這個例子中,PseudoLabeler類使用了XGBRegressor來實現偽標籤的迴歸。Sample_rate引數的預設值為0.2,意味著PseudoLabeler將會使用20%的無標籤資料集。
結果
為了測試PseudoLabeler,我使用XGBoost(當現場比賽時,使用XGBoost會得到最好的結果)。為了評估模型,我們將原始XGBoost與偽標籤XGBoost進行比較。使用8折交叉驗證(在4k資料量上,每折都有一個小資料集——大約500個數據)。評估指標是r2 - score,即比賽的官方評價指標。
PseudoLabeler的平均分略高,偏差較低,這使它(略微)優於原始模型。我在筆記本上做了一個更詳細的分析,可以在這裡看到。效能增長也許不高,但是要記住,Kaggle比賽中,每增加一個分數都有可能使你在排行榜上排名更高。這裡介紹的複雜性並不是太大(70行左右程式碼),但是這個示例中的問題和模型都很簡單,當試圖使用這個方法解決更復雜的問題或領域時要切記。
結論
偽標籤允許我們在訓練機器學習模型的同時使用偽標籤資料。這聽起來像是一種強大的技術,是的,它經常會增加我們的模型效能。然而,它可能很難調整以使它正常工作,即使它有效,也會帶來輕微的效能提升。在像Kaggle這樣的比賽中,我相信這項技術是很有用的,因為通常即使是輕微的分數提高也能讓你在排行榜上得到提升。儘管如此,在生產環境中使用這種方法之前,我還是會再三考慮,因為它似乎在沒有大幅度提高效能的情況下帶來了額外的複雜性,而這可能不是我們所希望看到的。
作者介紹:Vinko Kodžoman,資料和軟體愛好者,遊戲玩家和冒險家
Github:https://github.com/Weenkus
Email:[email protected]