作者 SHUBHAM JAIN
译者 钱亦欣
在有监督学习领域,我们已经取得了长足的进步,但这也意味着我们需要大量数据来做图像分类和销量预测,这些算法需要把这些数据扫描一遍又一遍来寻找模式。
.然而,这其实不是人类的学习方法,我们的大脑不需要成千上万的数据循环往复地学习来了解一类图片的主题,我们只需要少量的特征点来习得模式,所以现有的机器学习方法是有所缺陷的。
好在现有已经有一些针对这个问题的研究,我们或许可以构建一个系统,它只需要最少量的监督数据输入但能学得每个任务的主要模式。本文将会介绍其中一种名为伪标签学习的方法,我会深入浅出的讲解原理并演示一个案例。
走起!
注:我既定你已经对机器学习又基本了解,如果没有请学习相关知识再看本文。
假设我们目前面临一个简单的图像分类问题,我们的数据有两类标签(如下所示)。
我们的目标就是区分图像中有无日食,现在的问题就是如何仅从两幅图片的信息中构建一个分类系统。
一般而言,为了构建一个稳定的分类系统我们需要更多数据,我们从网上下载了更多相关图片来扩充我们的训练集。
但是,如果从监督学习的方法出发,我们还要给这些图片贴上标签,因此我们要借助人工完成这个过程。
基于这些数据运行了监督学习的算法,我们的模型表现显著高于那个仅基于两张图片的算法。
但是这个方法只在任务量不大的时候起效,数据量一大继续人工介入会消耗大量资源。
为了解决这一类问题,我们定义了一种名为半监督学习的方法,能从有标签(监督学习)和无标签数据(无监督学习)中共同习得模式。
来源: 链接
因此,现在就让我们学习下如何利用无标签数据。
考虑如下的情形
我们只有分属两个类别的两个数据点,途中的线代表着任意有监督模型的决策边界。
现在,让我们再途中加一些无标签数据,如下所示。
图片来源: 链接
如果我们注意到两幅图的差异,我们就可以发现有了无标签数据后,两个类别的决策边界变地更加精确了。
因此,使用无标签数据的优点如下:
现在,我们对于半监督学习已经有了直观的认识,当然这个领域也有许多种方法,本文就介绍其中的伪标签学习法。
这一技术,让我们不必再手工标注无标签数据,我们只需要基于有标签数据的技术来给出一个近似的标签,我们把这个过程分成多个步骤逐一介绍。
来源: 链接
我假设你已经看懂了上图的流程,第三步训练的模型将会用来给测试集分类。
为了让你有更好地理解,我会结合一个实际问题来讲解原理。
此处我们使用 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')
# 预处理
### 均值插值
train['Item_Weight'].fillna((train['Item_Weight'].mean()), inplace=True)
test['Item_Weight'].fillna((test['Item_Weight'].mean()), inplace=True)
### 把脂肪含量这一变量变为二分类
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'])
## 计算创立年份
train['Outlet_Establishment_Year'] = 2013 - train['Outlet_Establishment_Year']
test['Outlet_Establishment_Year'] = 2013 - test['Outlet_Establishment_Year']
### 查补 size 变量的缺失值
train['Outlet_Size'].fillna('Small',inplace=True)
test['Outlet_Size'].fillna('Small',inplace=True)
### 给 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)
## 去除 id 这一变量
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 = " {} (+/- {})".format(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 算法封装
'''
def __init__(self, model, unlabled_data, features, target, sample_rate=0.2, seed=42):
'''
@采样率 - 无标签样本中被用作伪标签样本的比率
'''
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):
'''
用伪标签样本填充数据集
'''
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):
'''
生成并返回包括标签样本和伪变迁样本的 augmented_train 集合
'''
num_of_samples = int(len(self.unlabled_data) * self.sample_rate)
# 训练模型并生成伪标签
self.model.fit(X, y)
pseudo_labels = self.model.predict(self.unlabled_data[self.features])
# 在测试集中加入伪标签
pseudo_data = self.unlabled_data.copy(deep=True)
pseudo_data[self.target] = pseudo_labels
# 将测试集中又伪标签的部分数据合并入训练集
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):
'''
返回预测值
'''
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: {} (+/- {})".format(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)这个参数,它代表无标签数据中本用作伪标签样本的比率。
下面我就就来测试下这个参数对伪标签学习法预测表现的影响。
为了说明这个参数的作用,我们来画个图。时间有限,我就只花了两个算法的图,你自己可以试试别的。
sample_rates = np.linspace(0, 1, 10)
def pseudo_label_wrapper(model):
return PseudoLabeler(model, test, features, target)
# 受试模型列表
model_factory = [
RandomForestRegressor(n_jobs=1),
XGBRegressor(),
]
# 对每个模型使用伪标签法
model_factory = map(pseudo_label_wrapper, model_factory)
# 用不同的采样率训练模型
results = {}
num_folds = 5
for model in model_factory:
model_name = model.get_model_name()
print('{}'.format(model_name))
results[model_name] = list()
for sample_rate in sample_rates:
model.sample_rate = sample_rate
# 计算 CV-3 R2 得分
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()
如此一来,我们发现对于不同的模型我们都要选取特定的采样率来达到最佳的效果,因而调节这个参数就显得很有必要了。
过去,半监督学习的应用场景着实有限,但如今在不少领域已经出现了它的身影。我找到的场景如下:
通常来讲,图片分类的目标是判断一幅图是否属于一个特定类别。但在这篇文章里,不光是图片本身,与之相关联的关键词夜奔由来提升半监督学习的分类质量。
来源: 链接
人口贩卖一直都是最恶劣的犯罪行为之一,需要全球一起关注。使用半监督学习可以比普通的方法提供更好的结果。
来源:链接
我希望你现在对班监督学习有了初步认识,并知道如何再具体问题中应用它了。不妨试着学习其他的半监督算法并和大家分享吧。
本文的所有的代码可以再我的 github 上找到。
原文链接