【问题标题】:Scalable or online out-of-core multi-label classifiers可扩展或在线核外多标签分类器
【发布时间】:2013-09-12 03:50:57
【问题描述】:

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

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

我正在使用 scikit-learn。对于特征提取,我之前使用的是 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 万份文档。散列需要 15 分钟,当涉及到 clf.fit(X, y) 时,我收到一个 MemoryError,因为 OvR 内部使用 LabelBinarizer 并且它试图分配一个几乎不可能分配的维度矩阵(y x 类)。我该怎么办?
  4. 还有其他具有可靠且可扩展的多标签算法的库吗?我知道 genism 和 mahout,但他们都没有任何多标签情况?

【问题讨论】:

  • 当你说“HashVectorizer 更好但不是那么可扩展”时只是一个评论:HashVectorizer 是完全可扩展的:如果你投入两倍的计算资源,你将更快地处理数据(你可以分区由于无状态和有限的内存使用,数据并并行运行处理)。这是可扩展性的确切定义。我同意HashVectorizer 可能会更优化,以便在相同的计算资源上更快地工作,但这与可伸缩性问题无关。
  • 感谢您的澄清。我确实同意 HV 确实优于 Tfidf,但我不确定数据分区部分。现在我做了一个小的 POC 来对数据进行分区并分别在各个部分上运行 HV,然后再组合结果。我最初的意思是算法部分的工作是一项了不起的成就,但它仍然可以像您建议的分区和并行运行一样变得更具可扩展性。 (完成后我会提交一个PR,让HV也有n_jobs参数)
  • 不幸的是,在 scikit-learn 中使用的 joblib 的当前实现中,我们使用多处理,因此必须复制输入数据才能发送到子进程。因此,这样的 n_jobs 参数会增加大量开销,并且可能根本没有好处。如果您确实拥有大型数据集,最好处理许多处理数据访问(磁盘、数据库、网络......)本身的并行核外循环,以避免任何内存复制。然而,这样的样板代码可能永远不会包含在 scikit-learn 中,因为它过于项目特定/框架化。

标签: machine-learning classification scikit-learn document-classification text-classification


【解决方案1】:

我关于可扩展性的论点是,您应该使用更高级的问题转换方法集合,而不是使用 OneVsRest,这只是最简单的基线。在我的paper 中,我提供了一个方案,用于将标签空间划分为子空间,并使用 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)

【讨论】:

    【解决方案2】:

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

    您的问题规模很大,因此很容易使用神经网络来解决它。看看magpie,它应该比线性分类器提供更好的结果。

    【讨论】:

      【解决方案3】:
      1. OneVsRestClassifier 实现的算法非常简单:当有 K 个类时,它只适合 K 个二元分类器。您可以在自己的代码中执行此操作,而不是依赖 OneVsRestClassifier。您也可以在最多 K 个内核上并行执行此操作:只需运行 K 个进程。如果您的机器中的课程多于处理器,您可以使用 GNU 并行等工具安排培训。
      2. scikit-learn 中的多核支持正在进行中; Python 中的细粒度并行编程非常棘手。 HashingVectorizer 有潜在的优化,但我(哈希代码的作者之一)还没有考虑到它。
      3. 如果您按照我(和 Andreas)的建议进行自己的一对一休息,那么这应该不再是问题了。
      4. (1.) 中的技巧适用于任何分类算法。

      至于特征的数量,这取决于问题,但对于大规模文本分类,2^10 = 1024 似乎非常小。我会尝试 2^18 - 2^22 左右的时间。如果您使用 L1 惩罚来训练模型,您可以在训练后的模型上调用 sparsify 以将其权重矩阵转换为更节省空间的格式。

      【讨论】:

      • 谢谢,我将尝试手动实现 OvR 并尝试规避可扩展性问题。我忘了说每个文档的长度都很小(200字左右)。所以,我认为 1024 个特征应该足够了,因为 2^18 给了我很多内存问题。我什至启动了一个 30 GB RAM 的 AWS 实例,但这也没有用。
      • 如果你有 35K 二元分类器和 2 ** 18 个特征,你将需要 73GB 来存储聚合模型。一旦学习了权重以在预测时节省内存,就有可能稀疏模型,但 AFAIK 尚未在 scikit-learn 中实现。您可以使用safe_sparse_dot 手动实现decision_function
      • 要训练具有许多零权重的模型,因此一旦将coef_ 属性存储为scipy.sparse 矩阵,这将提高内存使用率,您应该使用SGDClassifierpenalty="elasticnet" 或@ 987654332@.
      • @ogrisel:线性分类器有一个sparsify 方法,可以将coef_ 转换为稀疏矩阵格式(CSR)。
      • 太好了,我不确定。那么这就是去 Gaurav 的方式:scikit-learn.org/stable/modules/generated/…
      【解决方案4】:

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

      在 OneVsRestClassifier 上添加 partial_fit 将是一个不错的补充,实际上我认为它没有什么特别的问题。您也可以尝试自己实现并发送 PR。

      【讨论】:

      • 我并不感到惊讶 ;)
      • 谢谢,如果我要手动编写 OvR 代码,您会为这个问题推荐哪个估算器?另外,比如说,我启动了 35K 个估计器(n_labels)并将它们分别拟合到训练数据上。我将如何计算这些标签?那些具有单个 predict_proba > 0.5 的估计器将具有与该样本相关联的标签。这种方法会奏效吗? (抱歉,我在 ML 和 sklearn 才 3 周大)
      • 你可以尝试训练SGDClassifierPassiveAggressiveClassifier 的独立实例,也许MultinomialNB 作为二元分类器(每个标签一个)。然后,您可以根据 predict_probadecision_function 的值对排名靠前的预测进行排名,并取前 5 个标签(如果它们预测低于 0.5 概率或负决策函数,则更少)。您还可以训练第二个回归模型,该模型采用二元分类模型的概率,并预测每个实例要保留的正标签的预期数量(前 k 个中的 k 值)。
      • +1 用于线性模型(为什么要使用多项式而不是 Bernoulli olivier?)。我真的会首先尝试阈值,看看它是如何工作的。如果标签非常不平衡,您可能需要调整类权重。顺便说一句,35k 相当多。你可能会遇到记忆问题。请记住,您需要存储 n_labels * n_features 系数。
      • 非常感谢您提出的所有宝贵建议。我目前正在通过SGDClassifier 构建自定义多标签包装器。我正在使用decision_function,因为它们只有 1 个浮点值,而predict_proba 有 2 个值 - 一个用于 0,一个用于 1 类。我会尽快报告我的进展,或者如果我遇到任何问题。
      猜你喜欢
      • 2010-09-05
      • 1970-01-01
      • 2014-02-10
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 2018-12-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多