许多现实世界的分类问题都有不平衡的类分布。当数据严重不平衡时,分类算法将开始做出有利于多数类的预测。有几种方法可以解决类别不平衡问题。
一种方法是分配与类频率成反比的样本权重,以增加较少频率类在损失函数中的贡献。
另一种方法是使用过采样/欠采样技术。为少数类生成人工样本的流行技术是合成少数类过采样技术 (SMOTE) 和自适应合成 (ADASYN),两者都包含在 imblearn Python 库中。
建议Ë ntly,使用焦距损失的目标函数的提出。该技术被Tsung-Yi Lin 等人用于二元分类问题。[1]。
在这篇文章中,我将演示如何将 Focal Loss 合并到 LightGBM 分类器中以进行多类分类。代码可在GitHub上找到。
二元分类对于二元分类问题(标签 0/1),Focal Loss 函数定义如下:
Eq.1 焦点损失函数
其中_pₜ_是真实标签的函数。对于二元分类,该函数定义为:
Eq.2 类别概率
其中 pₜ 是通过将 sigmoid 函数应用于原始边距_z 获得的:_
Eq.3 用于将原始边距 z 转换为类概率 p 的 Sigmoid 函数
Focal Loss 可以解释为一个二元交叉熵函数乘以一个调制因子 (1- pₜ )^ γ,这减少了易于分类的样本的贡献。加权因子_aₜ_平衡了调制因子。引用作者的话:“当 γ = 2 时,与 CE 相比,分类为 pt = 0.9 的示例的损失将降低 100 倍,而当 pt ≈ 0.968 时,其损失将降低 1000 倍”。减少易于分类的示例的丢失,可以让训练更多地关注难以分类的示例”。
在 Max Halford 的博客 [ 2 ] 中可以找到一篇关于将 Focal Loss 纳入二元 LigthGBM 分类器的优秀文章。
多类分类有几种方法可以将 Focal Loss 合并到多类分类器中。形式上,调制和加权因子应该应用于分类交叉熵。这种方法需要提供关于原始边距_z_的多类损失的一阶和二阶导数。
另一种方法是使用 One-vs-the-rest (OvR),其中为每个类_C_训练一个二元分类器。来自_C_类的数据被视为正数,所有其他数据都被视为负数。在这篇文章中,我使用了 OvR 方法,重用了哈尔福德开发的二元分类器,没有做任何修改。
下面显示的类 oneVsRestLightGBMWithCustomizedLoss 封装了该方法:
import numpy as np from joblib import Parallel, delayed from sklearn.multiclass import _ConstantPredictor from sklearn.preprocessing import LabelBinarizer from scipy import special import lightgbm as lgb class OneVsRestLightGBMWithCustomizedLoss: def __init__(self, loss, n_jobs=3): self.loss = loss self.n_jobs = n_jobs def fit(self, X, y, **fit_params): self.label_binarizer_ = LabelBinarizer(sparse_output=True) Y = self.label_binarizer_.fit_transform(y) Y = Y.tocsc() self.classes_ = self.label_binarizer_.classes_ columns = (col.toarray().ravel() for col in Y.T) if 'eval_set' in fit_params: # use eval_set for early stopping X_val, y_val = fit_params['eval_set'][0] Y_val = self.label_binarizer_.transform(y_val) Y_val = Y_val.tocsc() columns_val = (col.toarray().ravel() for col in Y_val.T) self.results_ = Parallel(n_jobs=self.n_jobs)(delayed(self._fit_binary) (X, column, X_val, column_val, **fit_params) for i, (column, column_val) in enumerate(zip(columns, columns_val))) else: # eval set not available self.results_ = Parallel(n_jobs=self.n_jobs)(delayed(self._fit_binary) (X, column, None, None, **fit_params) for i, column in enumerate(columns)) return self def _fit_binary(self, X, y, X_val, y_val, **fit_params): unique_y = np.unique(y) init_score_value = self.loss.init_score(y) if len(unique_y) == 1: estimator = _ConstantPredictor().fit(X, unique_y) else: fit = lgb.Dataset(X, y, init_score=np.full_like(y, init_score_value, dtype=float)) if 'eval_set' in fit_params: val = lgb.Dataset(X_val, y_val, init_score=np.full_like(y_val, init_score_value, dtype=float), reference=fit) estimator = lgb.train(params=fit_params, train_set=fit, valid_sets=(fit, val), valid_names=('fit', 'val'), early_stopping_rounds=10, fobj=self.loss.lgb_obj, feval=self.loss.lgb_eval, verbose_eval=10) else: estimator = lgb.train(params=fit_params, train_set=fit, fobj=self.loss.lgb_obj, feval=self.loss.lgb_eval, verbose_eval=10) return estimator, init_score_value def predict(self, X): n_samples = X.shape[0] maxima = np.empty(n_samples, dtype=float) maxima.fill(-np.inf) argmaxima = np.zeros(n_samples, dtype=int) for i, (e, init_score) in enumerate(self.results_): margins = e.predict(X, raw_score=True) prob = special.expit(margins + init_score) np.maximum(maxima, prob, out=maxima) argmaxima[maxima == prob] = i return argmaxima def predict_proba(self, X): y = np.zeros((X.shape[0], len(self.results_))) for i, (e, init_score) in enumerate(self.results_): margins = e.predict(X, raw_score=True) y[:, i] = special.expit(margins + init_score) y /= np.sum(y, axis=1)[:, np.newaxis] return y
该类重新实现了 sklearn.multiclass 命名空间的 oneVsRestClassifier 类。重新实现原始 oneVsRestClassifier 类的主要原因是能够将附加参数转发给 fit 方法。当用户定义的评估函数没有改进时,这可用于传递评估集 (eval_set) 以停止训练,从而减少计算时间并避免过度拟合。
此外,该类使用通用 LightGBM 训练 API,这是在处理原始边距_z_和自定义损失函数时获得有意义的结果所必需的(有关更多详细信息,请参阅 [ 2 ])。如果没有这些限制,就可以更通用地实现该类,不仅可以接受任何损失函数,还可以接受任何实现 Scikit Learn 模型接口的模型。
该类的其他方法是 Scikit Learn 模型接口的一部分:fit、predict 和 predict_proba。在 predict 和 predict_proba 方法中,基本估计器返回原始边距_z_。请注意,当使用自定义损失函数时,LightGBM 返回原始边距_z_。类概率是使用 sigmoid 函数从边缘计算的,如等式所示。3.
一个例子让我们首先创建一个包含 3 个类的人工不平衡数据集,其中 1% 的样本属于第一类,1% 属于第二类,98% 属于第三类。像往常一样,数据集被分为训练集和测试集。
X, y = make_classification(n_classes=3, n_samples=2000, n_features=2, n_informative=2, n_redundant =0, n_clusters_per_class=1, weights=[.01, .01, .98], flip_y=.01, random_state=42) le = preprocessing.LabelEncoder() y_label = le.fit_transform(y) X_train, X_test, y_train, y_test = train_test_split(X, y_label, test_size=0.30, random_state=42) classes =[] labeles=np.unique(y_label) for v in labeles: classes.append('Class '+ str(v)) print(classes)
然后将模型拟合到列车数据上。为了保持实验简单,没有使用提前停止。得到的混淆矩阵如下所示:
图 1 使用 LGBMClassifier 在测试集上的混淆矩阵
对于第一个实验,在测试集上获得了 0.990 的准确度和 0.676 的召回值。使用 oneVsRestLightGBMWithCustomizedLoss 分类器和 Focal Loss 重复相同的实验。
from oneVsRestLightGBMWithCustomizedLoss import * from FocalLoss import FocalLoss #get the FocalLoss implementation from Halford's blog # Instantiate Focal loss loss = FocalLoss(alpha=0.75, gamma=2.0) # Not using early stopping clf = oneVsRestLightGBMWithCustomizedLoss(loss=loss) clf.fit(X_train, y_train) # Using early stopping #fit_params = {'eval_set': [(X_test, y_test)]} #clf.fit(X_train, y_train, **fit_params) y_test_pred = clf.predict(X_test) pred_accuracy_score = accuracy_score(y_test, y_test_pred) pred_recall_score = recall_score(y_test, y_test_pred, average='macro') print('prediction accuracy', pred_accuracy_score,' recall ', pred_recall_score) cnf_matrix = confusion_matrix(y_test, y_test_pred, labels=labeles) plot_confusion_matrix(cnf_matrix, classes=classes,normalize=True, title='Confusion matrix')
从上面的代码可以看出,损失函数完全可以在分类器之外配置,可以注入到类构造函数中。可以通过向 fit 方法提供包含 eval_set 的字典来打开提前停止,如上面的注释行所示。对于第二个实验,产生的混淆矩阵如下所示:
图 2 使用 oneVsRestLightGBMWithCustomizedLoss 分类器和 Focal Loss 在测试集上的混淆矩阵
在这种情况下,获得了 0.995 的准确度和 0.838 的召回值,比使用默认对数损失的第一个实验有所改进。这个结果从混淆矩阵中也很明显,其中 0 类的假阳性和 1 类的假阴性显着减少。
结论在这篇文章中,我展示了一种通过使用 One-vs-the-rest (OvR) 方法将 Focal Loss 纳入多类分类器的方法。
通过使用 Focal Loss,不需要样本权重平衡或人工添加新样本来减少不平衡。在人工生成的多类不平衡数据集上,使用 Focal loss 增加了召回值并消除了少数类中的一些误报和漏报。
该方法的有效性必须通过探索现实世界的数据集来确认,其中噪声和非信息特征预计会影响分类结果。
QQ学习群:1026993837 领资料
Focal Loss 和 LightGBM 多分类就为大家介绍到这里,欢迎学习《Python数据分析与机器学习项目实战》bye!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)