考虑未定义类型的多分类
考虑这样一个场景,我们要处理一个多分类问题,由于目标的空间是开放的,我们无法穷举所有的类别。目前我们制定了K类去训练一个多分类模型,在预测未知数据时,有可能出现未识别的类型,此时我们的多分类模型会赋予它k类中的一类。然鹅,这个赋予是错误的。那怎样去避免和处理这样的现象呢?
方案思考
遇到这个问题,很多人的第一直观想法是,分别给没类训练一个one-class classifier(比如one-class svm)不就好了。
这确实是一个看起来很不错的想法。但是one-class classifer本质上是做异常检测的,如果某一类的样本空间分布很分散,并没有聚成团,此时可能正确的样本都会被判断成错误的类别。实际中遇到各种乱七八糟的数据,我对one-class classifier的性能是打问号的。当然如果能精心设计,以上当我扯淡。
除了one-class classifer外,我粗略考虑到的两种稍微靠谱一点的做法,这里做一下记录。(貌似找到了这方面的paper,比如Unseen Class Discovery in Open-world Classification,之后找时间再写一篇学习下这类做法)
- 将multi-class改成multi-label任务
- 设计一个pipline,第一个分类器判断是否见过,第二就是常规的多分类
第二个做法中第一个分类器的设计,见仁见智。可以设计成one-class分类器,也可以计算与已知样本/类别的距离等这样简单的做法。
目前实践了第一种做法,做一下记录
multi-label
Multi-label指的是,一个样本可能有多个标签,不再是单一标签。比如一副海滩的图片,我们对其进行分类,其可能同时具备标签,白云、大海、沙滩、山、树。将其只分给其中一个比如大海,是不合适的。
扯个题外话,分类问题我觉得可以分为四类
- Binary classification
- Multi-class classification
- Multi-label classification
- Multi-target classification
提供两个工具:scikit-multilearn、meka
Multi-label算法的核心其实是想利用label和label之间的相关性,我们这里处理未见类别其实没有利用到这个思想。
具体算法可以分为如下几类
- Binary Relevance:每个标签看作一个单独的类分类问题。
- Classifier Chain:单独分类每个类别,但是前面得分类伪标签会作为后面分类的特征。(利用相关性,需要组合不同的链)
- Label Powerset:组合类别做多个多分类问题
- Adaptive:这类就比较丰富了,MLKNN、神经网络、集成学习
在本次考虑的场景下,我们并没有考虑类别之间的关系,所以采用BR做一个测试,本次没有使用scikit-multilearn,仅仅用了sklearn的multi-label工具。
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import ShuffleSplit, train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.metrics import classification_report
from sklearn.preprocessing import LabelBinarizer, LabelEncoder
from sklearn.multiclass import OneVsRestClassifier
import numpy as np
def train_one_vs_rest(self):
all_data_file = 'data.txt'
data_all, labels_all = self.process_data(all_data_file)
# lb = LabelEncoder()
lb = LabelBinarizer()
y = lb.fit_transform(labels_all)
print(lb.classes_)
text_train, text_test, y_train, y_test = \
train_test_split(data_all, y, test_size=0.33, random_state=0)
count_vect = CountVectorizer()
train_vec = count_vect.fit_transform(text_train)
clf = OneVsRestClassifier(LogisticRegression())
clf.fit(train_vec, y_train)
print(len(clf.estimators_))
test_vec = count_vect.transform(text_test)
pred = clf.predict(test_vec)
print(classification_report(y_test, pred))
self.model = clf
self.vectorizer = count_vect
pred = self.pred_multi_label(test_vec)
print(pred)
def pred_multi_label(self, vectors):
preds = []
for i in range(len(self.model.estimators_)):
preds.append(
self.model.estimators_[i].predict_proba(vectors)[:, 1])
return np.column_stack(tuple(preds))
这里需要注意的是,在预测过程中,如果采用OneVsRestClassifier默认的predictproba,得到的没类的概率是归一化的。我想得到的就是具体属于每一类的概率,不要归一化。所以,这里采用了其属性`estimators`来计算具体的概率。
实验发现,效果贼好。对于已知类别,对应类上的概率基本大于80%,而对于未见类别,最高也在57%左右。
此时我们完全可以采用70%去截断,如果一个类别未达到,那就是未知类别了。
当然,这种做法还有一些问题,接下来梳理下更好的做法。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!