问题 可扩展或在线的核外多标签分类器


在这个问题上,过去2-3周我一直在绞尽脑汁。 我有一个多标签(不是多类)问题,每个样本都可以属于几个标签。

我有大约450万个文本文档作为训练数据,大约100万个作为测试数据。标签大约35K。

我在用 scikit学习。对于特征提取,我之前使用的TfidfVectorizer根本没有扩展,现在我使用的是HashVectorizer,考虑到我拥有的文档数量,它更好但不具备可扩展性。

vect = HashingVectorizer(strip_accents='ascii', analyzer='word', stop_words='english', n_features=(2 ** 10))

SKlearn提供OneVsRestClassifier,我可以在其中提供任何估算器。对于多标签,我发现LinearSVC和SGDClassifier只能正常工作。根据我的基准,SGD在内存和时间方面都优于LinearSVC。所以,我有这样的事情

clf = OneVsRestClassifier(SGDClassifier(loss='log', penalty='l2', n_jobs=-1), n_jobs=-1)

但这有一些严重的问题:

  1. OneVsRest没有partial_fit方法,这使得无法进行核外学习。那有什么替代品吗?
  2. HashingVectorizer / Tfidf都可以在单个核心上运行,并且没有任何n_jobs参数。散列文档需要花费太多时间。任何替代/建议? n_features的值是否正确?
  3. 我测试了100万份文件。 Hashing需要15分钟,当涉及到clf.fit(X,y)时,我收到一个MemoryError,因为OvR在内部使用LabelBinarizer,它试图分配一个维度矩阵(y x类),这是不可能分配的。我该怎么办?
  4. 还有其他任何具有可靠和可扩展的多标签算法的库吗?我知道genism和mahout但他们两个都没有任何多标签情况?

1263
2017-09-08 14:43


起源

当你说“HashVectorizer更好但不具备可扩展性”时,请注意: HashVectorizer 是完全可扩展的:如果你投入两倍的计算资源,你将加快数据处理速度两倍(你可以对数据进行分区并并行运行处理,这要归功于它的无状态和有限的内存使用)。这是可伸缩性的确切定义。我同意 HashVectorizer 可能会更优化以在相同的计算资源上更快地工作,但这与可伸缩性问题无关。 - ogrisel
谢谢你的澄清。我同意HV比Tfidf更有优势,我不确定数据分区部分。现在我做了一个小的POC来分割数据并分别在部件上运行HV,然后再合并结果。我最初的意思是算法部分的工作是一项伟大的成就,但仍然可以像你建议的那样进行分区和并行运行。 (在我完成之后,我将提交一个PR,以便HV也有一个n_jobs参数) - Gaurav Kumar
不幸的是,在scikit-learn中使用的joblib的当前实现中,我们使用多处理,因此必须复制输入数据以将其发送到子进程。因此,这样的n_jobs参数会增加显着的开销,并且可能根本不是有益的。如果你真的有大型数据集,最好处理许多处理数据访问(磁盘,数据库,网络......)的并行核外循环,以避免任何内存复制。然而,这样的锅炉板代码可能永远不会被包含在scikit-learn中,因为太具体项目/框架。 - ogrisel


答案:


我会手工做多标签部分。无论如何,OneVsRestClassifier将它们视为独立问题。您可以创建n_labels许多分类器,然后在它们上调用partial_fit。但是,如果您只想哈希一次(我建议),则不能使用管道。 不确定加速哈希矢量化器。你得问@Larsmans和@ogrisel;)

partial_fit 在OneVsRestClassifier上是一个很好的补充,实际上我没有看到它的特殊问题。您也可以尝试自己实现并发送PR。


7
2017-09-08 15:02



我只是打字:) - Fred Foo
我并不感到意外 ;) - Andreas Mueller
谢谢,如果我是手动编码OvR,你会推荐哪个估算器来解决这个问题?另外,比方说,我启动35K估算器(n_labels)并单独将它们放在训练数据上。我如何计算这些标签?具有个体predict_proba> 0.5的那些估计器将具有与该样本相关联的标签。这种方法会起作用吗? (对不起,我在ML&sklearn只有3周大了) - Gaurav Kumar
你可以尝试训练独立的实例 SGDClassifier 和 PassiveAggressiveClassifier 有可能 MultinomialNB 作为二元分类器(每个标签一个)。然后,您可以根据的值对最高预测进行排名 predict_proba 要么 decision_function 并采用前5个标签(如果他们预测低于0.5的概率或否定决策函数,则更少)。您还可以训练第二个回归模型,该模型采用二元分类模型的概率,并预测每个实例保留的正标签的预期数量(前k中的k值)。 - ogrisel
对于线性模型+1(为什么你会使用多项式而不是伯努利奥利维尔?)。我真的会首先尝试阈值,看看它是如何工作的。如果标签非常不平衡,您可能需要调整类权重。顺便说一句,35k是相当多的。你可能会遇到内存问题。请记住,您需要存储n_labels * n_features系数。 - Andreas Mueller


  1. 那个算法 OneVsRestClassifier 工具很简单:它很合适 ķ 二元分类器有的时候 ķ 类。您可以在自己的代码中执行此操作,而不是依赖于此 OneVsRestClassifier。你最多也可以这样做 ķ 并行核心:运行 ķ 流程。如果您的计算机中有多个类而不是处理器,则可以使用GNU parallel等工具安排培训。
  2. scikit-learn中的多核支持正在进行中; Python中的细粒度并行编程非常棘手。有潜在的优化 HashingVectorizer但是我(其中一个哈希码的作者)还没有完成它。
  3. 如果你按照我(和安德烈亚斯)的建议做自己的一对一休息,这应该不再是问题了。
  4. (1.)中的技巧适用于任何分类算法。

至于功能的数量,它取决于问题,但对于大规模的文本分类2 ^ 10 = 1024似乎 非常 小。我会尝试2 ^ 18 - 2 ^ 22左右的东西。如果你训练一个带有L1惩罚的模型,你可以打电话 sparsify 在训练有素的模型上将其权重矩阵转换为更节省空间的格式。


8
2017-09-08 15:04



谢谢,我将尝试手动实现OvR,并试图规避可扩展性问题。我忘了提到每个文件的长度都很小(200字左右)。所以,我认为1024个功能应该足够了,因为2 ^ 18给了我很多内存问题。我甚至开始启动30 GB RAM的AWS实例,但这也没有用。 - Gaurav Kumar
如果您有35K二进制分类器,具有2 ** 18个功能,则只需存储73GB即可存储聚合模型。一旦在预测时学习权重以节省内存,就可以对模型进行稀疏化,但是在scikit-learn中尚未实现AFAIK。你可以实现 decision_function 手动用 safe_sparse_dot 要做到这一点。 - ogrisel
训练具有许多零权重的模型,这将导致一旦改进的内存使用 coef_ 属性存储为 scipy.sparse 矩阵,你应该使用 SGDClassifier 同 penalty="elasticnet" 要么 "l1"。 - ogrisel
@ogrisel:线性分类器有一个 sparsify 转换的方法 coef_ 到稀疏矩阵格式(CSR)。 - Fred Foo
太好了,我不确定。那么这就是去Gaurav的方法: scikit-learn.org/stable/modules/generated/... - ogrisel


我对可伸缩性的论证是,不应该使用OneVsRest,它只是最简单的最简单的基线,而应该使用更高级的问题转换方法集合。在我的  我提供了一种方案,用于将标签空间划分为子空间,并使用Label Powerset将子问题转换为多类单标签分类。要尝试这一点,只需使用以下代码,该代码利用在scikit-learn之上构建的多标签库 - scikit-multilearn

from skmultilearn.ensemble import LabelSpacePartitioningClassifier
from skmultilearn.cluster import IGraphLabelCooccurenceClusterer
from skmultilearn.problem_transform import LabelPowerset

from sklearn.linear_model import SGDClassifier

# base multi-class classifier SGD
base_classifier = SGDClassifier(loss='log', penalty='l2', n_jobs=-1)

# problem transformation from multi-label to single-label multi-class
transformation_classifier = LabelPowerset(base_classifier)

# clusterer dividing the label space using fast greedy modularity maximizing scheme
clusterer = IGraphLabelCooccurenceClusterer('fastgreedy', weighted=True, include_self_edges=True) 

# ensemble
clf = LabelSpacePartitioningClassifier(transformation_classifier, clusterer)

clf.fit(x_train, y_train)
prediction = clf.predict(x_test)

1
2018-02-16 23:29





partial_fit() 方法是 最近 添加到 sklearn 所以希望它应该在即将发布的版本中可用(它已经在master分支中)。

问题的大小使得用神经网络解决问题变得很有吸引力。看一下 ,它应该比线性分类器提供更好的结果。


0
2017-08-31 12:07