scikit-learn:将数据拟合成块与一次拟合

Posted

技术标签:

【中文标题】scikit-learn:将数据拟合成块与一次拟合【英文标题】:scikit-learn: fitting data into chunks vs fitting it all at once 【发布时间】:2015-10-25 08:47:44 【问题描述】:

我正在使用 scikit-learn 构建一个分类器,该分类器适用于(有点大的)文本文件。我目前需要一个简单的词袋特征,因此我尝试使用 TfidfVectorizer/HashingVectorizer/CountVectorizer 来获取特征向量。

但是,一次处理整个训练数据以获得特征向量会导致 numpy/scipy 中的内存错误(取决于我使用的向量化器)。所以我的问题是:

从原始文本中提取文本特征时:如果我将数据以块的形式拟合到矢量化器中,这是否与一次拟合整个数据相同?

用代码来说明这一点,如下:

vectoriser = CountVectorizer() # or TfidfVectorizer/HashingVectorizer
train_vectors = vectoriser.fit_transform(train_data)

与以下不同:

vectoriser = CountVectorizer() # or TfidfVectorizer/HashingVectorizer


start = 0
while start < len(train_data):
    vectoriser.fit(train_data[start:(start+500)])
    start += 500

train_vectors = vectoriser.transform(train_data)

在此先感谢,如果这个问题完全迟钝,我们深表歉意。

【问题讨论】:

代码中是否有任何地方可以释放内存,例如通过摆脱对已解析文本数据的引用?或者您是否可以切换到 64 位 Python 和/或不同的操作系统(例如,Windows 会限制分配给每个进程的内存,Ubuntu 不会)。 @AleksanderLidtke,感谢您的评论。回答您的问题: 1. 我无法释放更多内存。我不会将整个文本文件加载到内存中,我只会将文件名传递给 scikit-learn 矢量化器。这意味着如果可以在 scikit-learn 内部进行任何内存使用优化。 2. 换成其他操作系统会有问题。 3.我不认为问题是因为使用了太多的内存。我以前见过我的 python 应用程序使用高达 1.8GB 的​​ RAM,而现在它在大约 500MB 时崩溃。我应该打开另一个问题并发布我收到的异常跟踪吗? 没问题。没错,我明白了。我不知道 scikit 的底层工作原理,我只使用了它的一部分。也许您可以自己读取数据,然后提取相关位,并使用那些具有 scikit 内存效率更高功能的位?还是写信给开发商?此外,内存不足的进程可能是 Python 进程的子进程。查看运行脚本时是否有任何东西占用大量内存。 @DarkMatter scikit-learn 使用我的所有 8GB RAM(以及更多)没有问题。您可能会遇到内存错误,因为它想在内存中分配一个巨大的矩阵。所以 RAM 实际上从未使用过,但您仍然有内存问题。对我来说,这似乎是一个合理的问题;)。 【参考方案1】:

这取决于您使用的矢量化器。

CountVectorizer 计算文档中单词的出现次数。 它为每个文档输出一个(n_words, 1) 向量,其中包含每个单词在文档中出现的次数。 n_words文档中的单词总数(也就是词汇的大小)。 它还适合一个词汇表,以便您可以内省模型(查看哪个词是重要的,等等)。你可以使用vectorizer.get_feature_names()查看它。

当您将其放入前 500 个文档时,词汇表将仅由 500 个文档中的单词组成。假设有 30k 个,fit_transform 输出一个 500x30k 稀疏矩阵。 现在你 fit_transform 再次使用 500 个下一个文档,但它们只包含 29k 个单词,所以你得到一个 500x29k 矩阵... 现在,您如何对齐矩阵以确保所有文档具有一致的表示? 目前我想不出一个简单的方法来做到这一点。

使用 TfidfVectorizer 您还有另一个问题,那就是逆文档频率:为了能够计算文档频率,您需要一次查看所有文档。 但是TfidfVectorizer 只是CountVectorizer 后跟TfIdfTransformer,因此如果您设法获得CountVectorizer 的正确输出,则可以在数据上应用TfIdfTransformer

使用 HashingVectorizer 情况有所不同:这里没有词汇。

In [51]: hvect = HashingVectorizer() 
In [52]: hvect.fit_transform(X[:1000])       
<1000x1048576 sparse matrix of type '<class 'numpy.float64'>'
 with 156733 stored elements in Compressed Sparse Row format>   

这里在前 1000 个文档中没有 1M+ 个不同的单词,但我们得到的矩阵有 1M+ 个列。HashingVectorizer 不会将单词存储在内存中。这使其内存效率更高,并确保它返回的矩阵总是具有相同的列数。 所以你不会遇到与CountVectorizer 相同的问题。

这可能是您描述的批处理的最佳解决方案。有几个缺点,即您无法获得 idf 权重,并且您不知道单词和您的特征之间的映射。

HashingVectorizer documentation 引用了一个执行 out-of-core classification on text data 的示例。它可能有点乱,但它可以做你想做的事情。

希望这会有所帮助。

编辑: 如果你有太多数据,HashingVectorizer 是要走的路。 如果您仍想使用CountVectorizer,一种可能的解决方法是自己调整词汇表并将其传递给您的矢量化器,这样您只需要调用tranform

这是一个你可以修改的例子:

import re
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

news = fetch_20newsgroups()
X, y = news.data, news.target

现在行不通的方法:

# Fitting directly:
vect = CountVectorizer()
vect.fit_transform(X[:1000])
<1000x27953 sparse matrix of type '<class 'numpy.int64'>'
with 156751 stored elements in Compressed Sparse Row format>

注意我们得到的矩阵的大小。 “手动”拟合词汇:

def tokenizer(doc):
    # Using default pattern from CountVectorizer
    token_pattern = re.compile('(?u)\\b\\w\\w+\\b')
    return [t for t in token_pattern.findall(doc)]

stop_words = set() # Whatever you want to have as stop words.
vocabulary = set([word for doc in X for word in tokenizer(doc) if word not in stop_words])

vectorizer = CountVectorizer(vocabulary=vocabulary)
X_counts = vectorizer.transform(X[:1000])
# Now X_counts is:
# <1000x155448 sparse matrix of type '<class 'numpy.int64'>'
#   with 149624 stored elements in Compressed Sparse Row format>
#   
X_tfidf = tfidf.transform(X_counts)

在您的示例中,您需要先构建整个矩阵 X_counts(适用于所有文档),然后再应用 tfidf 转换。

【讨论】:

很好的解释,谢谢!我想如果我对训练数据进行两次传递,在第一次只做“拟合”,然后在第二次做“转换”,这可以解决你提到的 CountVectorizer 问题,因为这首先建立词汇表,然后给出向量.然而,这似乎效率低得多,因为 fit 在 CountVectorizer 中调用 fit_transform (?),所以我基本上做了两次相同的工作。我仍然不太确定的是:所有这些都可以与 TfidfVectorizer 一起使用吗? 如果您可以一次容纳所有文件,则执行 2 次通行证是可行的。否则,您可以“手动”收集词汇并将其传递给 CountVectorizer 构造函数,以便它使用固定词汇。要获得 TfidfVectorizer,您只需在 CountVectorizer 的输出上使用 TfidfTransformer。我很惊讶地看到 fit 确实调用了 fit_transform!一种可能的解决方法是遍历您的文档以自己构建词汇表。 为什么我需要一次装下所有文件?我认为发生的情况是,每次安装另一批时,词汇量就会增加。因此,当稍后转换数据时,使用从所有文档中提取的最终词汇表,甚至是块中的词汇表。对不起,如果我没有看到明显的东西:( 否则,谢谢您的回答。我会接受它,但请在此处更新我们对对话的想法,以便对其他人尽可能有帮助:) 不幸的是,每次调用 fit 时,矢量化器都是从头开始拟合的。不过,您描述的行为在您的情况下会很好。我建议您自己构建词汇表,我将为此编辑我的答案。否则,如果您有太多数据,HashingVectorizer 就是要走的路。【参考方案2】:

我不是文本特征提取专家,但基于文档和我的其他分类器基础经验:

如果我对训练数据块进行多次拟合,那会是 和一次拟合整个数据一样吗?

你不能直接合并提取的特征,因为你会得到不同的重要性,即weights对于从不同中的不同块获得的相同标记/单词与块中其他单词的比例,用 不同 键表示。

你可以使用任何特征提取方法,我认为结果的有用性取决于任务。

但是如果您可以使用不同块的不同特征对相同数据进行分类。一旦您使用相同的特征提取方法(或者您也可以使用不同的提取方法)获得了几个不同的输出,您可以将它们用作“合并”机制的输入,例如@ 987654322@、boosting等 实际上,在大多数情况下,在完成上述整个过程之后,您将获得更好的最终输出,而不是在一个“功能齐全”但甚至是简单的分类器中输入完整文件。

【讨论】:

我必须说我不完全确定我理解你的答案。使用集成方法进行分类听起来确实是个好主意。但是,这并不能回答我关于如何提取特征(也包括未见过的数据)的问题。我可以很容易地在训练过程中为不同的“突发”使用不同的矢量化器,但是当看不见的数据到达时我应该使用哪个矢量化器,我想把它变成一个特征向量来分类?我之前没有实现过 bagging 之类的方法,所以答案可能很明显...... @DarkMatter 您的回答完全如下:“所以我的问题是:如果我对训练数据块进行多次拟合,这是否与一次拟合整个数据相同? ?”,但请更新您的问题,以完全符合您的需求,我会尝试回答,或者您可以打开一个新问题。 感谢您的评论。我相应地更新了问题,希望现在更清楚了。代码示例最好地解释了我的要求。 @DarkMatter 我现在看到我回答了你的问题,但可能有点模糊,所以我更新了我的答案,参见“你获得的功能”。

以上是关于scikit-learn:将数据拟合成块与一次拟合的主要内容,如果未能解决你的问题,请参考以下文章

如何利用matlab将数值拟合成函数

离散点怎么拟合成曲线啊

怎么通过一组数据拟合出总体分布

autocad中怎么 把几条直线拟合成曲线

如何用MATLAB对二组数据同时进行曲线拟合

性能评价