对于每个机器学习项目而言,数据是基础,是不可或缺的一部分。在本文中,作者将会展示一个名为伪标签的简单的半监督学习方法,它可以通过使用无标签数据来提高机器学习模型的性能。
伪标签
为了训练机器学习模型,在监督学习中,数据必须是有标签的。那这是否意味着无标签的数据对于诸如分类和回归之类的监督任务就无用了呢?当然不是! 除了使用额外数据进行数据分析,还可以将无标签数据和标签数据结合起来,一同训练半监督学习模型。
该方法的主旨思想其实很简单。首先,在标签数据上训练模型,然后使用经过训练的模型来预测无标签数据的标签,从而创建伪标签。此外,将标签数据和新生成的伪标签数据结合起来作为新的训练数据。
这个方法的灵感来自于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:vinko.kodzoman@yahoo.com