Scikit-Learn 的 Pipeline:传递了一个稀疏矩阵,但需要密集数据

Posted

技术标签:

【中文标题】Scikit-Learn 的 Pipeline:传递了一个稀疏矩阵,但需要密集数据【英文标题】:Scikit-Learn's Pipeline: A sparse matrix was passed, but dense data is required 【发布时间】:2015-04-07 17:04:36 【问题描述】:

我发现很难理解如何修复我创建的管道(阅读:主要从教程中粘贴)。这是python 3.4.2:

df = pd.DataFrame
df = DataFrame.from_records(train)

test = [blah1, blah2, blah3]

pipeline = Pipeline([('vectorizer', CountVectorizer()), ('classifier', RandomForestClassifier())])

pipeline.fit(numpy.asarray(df[0]), numpy.asarray(df[1]))
predicted = pipeline.predict(test)

当我运行它时,我得到:

TypeError: A sparse matrix was passed, but dense data is required. Use X.toarray() to convert to a dense numpy array.

这是给pipeline.fit(numpy.asarray(df[0]), numpy.asarray(df[1]))这一行的。

我已经通过 numpy、scipy 等尝试了很多解决方案,但我仍然不知道如何解决它。是的,以前也出现过类似的问题,但不是在管道中。 我必须在哪里申请toarraytodense

【问题讨论】:

【参考方案1】:

使用此管道添加 TfidTransformer plus

        pipelinEx = Pipeline([('bow',vectorizer),
                           ('tfidf',TfidfTransformer()),
                           ('to_dense', DenseTransformer()), 
                           ('classifier',classifier)])

上面的第一行,以稀疏矩阵形式获取文档的字数。但是,在实践中,您可能会使用 TfidfTransformer 在一组新的未见过文档上计算 tfidf 分数。 然后,通过调用 tfidf transformer.transform(vectorizer),您最终将计算文档的 tf-idf 分数。在内部,这是计算 tf * idf 乘法,其中词频由其 idf 值加权。

【讨论】:

【参考方案2】:

最简洁的解决方案是使用FunctionTransformer 转换为密集:这将自动实现fittransformfit_transform 方法,如David 的回答。此外,如果我的管道步骤不需要特殊名称,我喜欢使用 sklearn.pipeline.make_pipeline 便利功能来启用更简约的语言来描述模型:

from sklearn.preprocessing import FunctionTransformer

pipeline = make_pipeline(
     CountVectorizer(), 
     FunctionTransformer(lambda x: x.todense(), accept_sparse=True), 
     RandomForestClassifier()
)

【讨论】:

我刚试过这个,看到FunctionTransformeraccept_sparse参数。您需要将其设置为True 对于和我一样使用@maxymoo 解决方案的你们,可以导入FunctionTransformer from sklearn.preprocessing import FunctionTransformer 添加 FunctionTransformer 时出现错误:AttributeError: Can't pickle local object 'main large.<locals>.<lambda>' pipeline。有关如何修复它的任何提示? @guido 使用 dill 而不是 pickle @Guido 我猜你正试图在一些交叉验证/网格搜索中使用管道。在引擎盖下,管道被腌制了,问题是lambda 函数不能被腌制。因此,您必须将lambda 功能提取到常规函数def to_dense(x): 并使用它来代替lambda【参考方案3】:

不幸的是,这两者是不相容的。 CountVectorizer 产生一个稀疏矩阵,而 RandomForestClassifier 需要一个密集矩阵。可以使用X.todense() 进行转换。这样做会大大增加您的内存占用。

以下是基于http://zacstewart.com/2014/08/05/pipelines-of-featureunions-of-pipelines.html 执行此操作的示例代码,它允许您在管道阶段调用.todense()

class DenseTransformer(TransformerMixin):

    def fit(self, X, y=None, **fit_params):
        return self

    def transform(self, X, y=None, **fit_params):
        return X.todense()

拥有DenseTransformer 后,您可以将其添加为管道步骤。

pipeline = Pipeline([
     ('vectorizer', CountVectorizer()), 
     ('to_dense', DenseTransformer()), 
     ('classifier', RandomForestClassifier())
])

另一种选择是使用适用于稀疏数据的分类器,例如LinearSVC

from sklearn.svm import LinearSVC
pipeline = Pipeline([('vectorizer', CountVectorizer()), ('classifier', LinearSVC())])

【讨论】:

非常感谢!我正在尝试不同的分类器,部分是为了学习,部分是为了找到最有效的分类器。说实话,就我而言,多项式 NB 获得了迄今为止最好的结果。我将尝试您的代码,非常感谢您提供详尽的答案。 听起来很有趣。 RandomForest 适用于密集的数字数据。我发现它不能很好地扩展稀疏文本功能。如果您确实想在文本上尝试它,您可以尝试先添加一个特征选择阶段。这有时可以很好地工作。我最喜欢的文本是使用 loss='modified_huber' 或 loss='log' 的 LinearSVC 和 SGDClassifier。 使用 SGD 的基于 clasifer 的词性标注应用程序要使用哪些参数?【参考方案4】:

0.16-dev 中的随机森林现在接受稀疏数据。

【讨论】:

【参考方案5】:

您可以使用.values 方法将pandas Series 更改为数组。

pipeline.fit(df[0].values, df[1].values)

但是我认为这里的问题发生是因为CountVectorizer() 默认返回一个稀疏矩阵,并且不能通过管道传输到 RF 分类器。 CountVectorizer() 确实有一个 dtype 参数来指定返回数组的类型。也就是说,通常您需要进行某种降维才能使用随机森林进行文本分类,因为词袋特征向量非常长

【讨论】:

我明白了,非常感谢,现在说得通了。我尝试为你投票,但我的声望不够?

以上是关于Scikit-Learn 的 Pipeline:传递了一个稀疏矩阵,但需要密集数据的主要内容,如果未能解决你的问题,请参考以下文章

Scikit-Learn 的 Pipeline:传递了一个稀疏矩阵,但需要密集数据

使用 scikit-learn Pipeline 和 GridSearchCV 时出错

如何检查 Scikit-Learn Pipeline 所做的更改?

如何在 scikit-learn 中提取 MultinomialNB Pipeline 训练模型中的单词特征?

在 scikit-learn 管道中插入或删除步骤

Scikit-Learn Pipeline 中的新功能 - 两个现有功能之间的交互