将文档分类为不同类别

34

我在Postgres数据库中存储了大约300k个文档,并为它们打上了主题类别标签(总共大约有150个类别)。我还有另外150k个文档没有分类。我正在尝试寻找最佳的编程方法来对它们进行分类。

我一直在探索NLTK及其朴素贝叶斯分类器。这似乎是一个很好的起点(如果您可以为此任务提出更好的分类算法,我会认真听取建议)。

我的问题是,我没有足够的RAM可以一次性训练所有150个类别/ 300k个文档的NaiveBayesClassifier(使用5个类别进行训练需要8GB内存)。此外,随着我训练的类别数量增加,分类器的准确度似乎会下降(2个类别的精度为90%,5个为81%,10个为61%)。

我应该只针对每次使用5个类别训练分类器,并将所有150k个文档通过分类器运行以查看是否有匹配项吗?这似乎可以工作,但是会有很多误报,因为那些不真正匹配任何类别的文档被分类器强行分配到最佳匹配项中。是否有一种方法可以为分类器提供“无匹配”选项,以防文档不适合任何类别?

这是我的测试类:http://gist.github.com/451880


也许在线/增量训练模式可以解决内存问题:http://en.wikipedia.org/wiki/Online_machine_learning - Amro
3个回答

33

您应该首先将文档转换为TF-log(1 + IDF)向量:术语频率很稀疏,因此您应该使用Python字典,以术语作为键,计数作为值,然后除以总计数以获得全局频率。

另一种解决方案是使用abs(hash(term))作为正整数键。然后您可以使用scipy.sparse向量,这些向量比Python字典更方便且更有效地执行线性代数运算。

还要通过对属于同一类别的所有标记文档的频率进行平均来构建150个频率向量。然后对于要标记的新文档,您可以计算文档向量与每个类别向量之间的余弦相似度,并选择最相似的类别作为文档的标签。

如果这还不够好,那么您应该尝试使用L1惩罚训练逻辑回归模型,如scikit-learn此示例所述(这是@ephes解释的liblinear的包装器)。用于训练逻辑回归模型的向量应该是先前介绍的TD-log(1+IDF)向量,以获得良好的性能(精确度和召回率)。scikit learn lib提供了一个sklearn.metrics模块,用于计算给定模型和给定数据集的这些分数。

对于更大的数据集:您应该尝试使用vowpal wabbit,这可能是地球上最快的兔子,用于大规模文档分类问题(但据我所知,不易使用Python包装器)。


2
Vowpal Wabbit非常快。但我们仍然使用批量训练而不是在线学习算法,因为liblinear(经过适当优化)只需要几分钟就可以处理数百万个文档(我们mmaped(共享)特征向量,以便新的训练或分类进程不必解析文件,而只需循环遍历主内存),并且它的性能更好(我现在没有数字...)。 - ephes
2
同意,当数据流是无限的并且不再适合内存时,vowpal wabbit确实非常有趣,例如来自流行网络邮件提供商的“报告垃圾邮件”按钮 :) - ogrisel
2
除此之外,质心分类并不比朴素贝叶斯好多少。这篇论文http://www2009.org/proceedings/pdf/p201.pdf是错误的。我们告诉他们由于一个漏洞,他们使用了测试数据进行训练,但是讨论没有任何进展...线性支持向量机仍然是最先进的技术。 - ephes
@gilesec 的线性支持向量机并不太过,它们是最好的选择(连同带有 L1 和/或 L2 惩罚项的逻辑回归):它们都是线性模型(因此使用稀疏数据结构实现时效率高),可以通过使用随机梯度下降求解原始问题以在线方式轻松实现,或者像liblinear中的更智能的求解器一样。在线性SVM和逻辑回归之间进行选择,只是损失函数和正则化程序发生了变化。它们都直接估计MAP,因此不会受到朴素贝叶斯偏差的影响。 - ogrisel
你能定义一下平均频率吗?我可以直接取总频率并进行归一化处理吗? - paparazzo
显示剩余4条评论

11

你的文档有多大(字数)?在训练150K个文档时,内存消耗不应该成为问题。

朴素贝叶斯是一个很好的选择,特别是当你有许多类别,但只有少量训练样例或非常嘈杂的训练数据时。但总体而言,线性支持向量机的表现要更好一些。

你的问题是多类别问题(一个文档属于一个类别),还是多标签问题(一个文档属于一个或多个类别)?

准确率不是判断分类器性能的好选择。你应该使用精度 vs 召回率、精度召回率平衡点(prbp)、f1、auc,并查看基于置信阈值的召回率(x)和精度(y)的精度 vs 召回率曲线(文档是否属于某一类别)。通常情况下,你将为每个类别构建一个二元分类器(一个类别的正训练样本 vs 所有其他不属于当前类别的训练样本)。你将需要为每个类别选择一个最佳的置信阈值。如果你想将这些单个类别的度量组合成全局性能度量,则必须进行微观平均(总结所有真阳性、假阳性、假阴性和真阴性,计算组合分数)或宏观平均(计算每个类别的得分,然后将这些得分平均)。

我们拥有数千万篇文档、数百万个训练样例和数千个类别(多标签)。由于我们面临严重的训练时间问题(每天新增、更新或删除的文档数量相当高),因此我们使用了修改版的liblinear。但对于较小的问题,使用liblinear周围的一些Python包装器(如liblinear2scipyscikit-learn)应该也能很好地工作。


文档可以是“多标签”的,平均长度约为500-1000个单词。 - erikcw
1
好的,那么按照@ogrisel建议使用稀疏tfidf向量,并为每个类别使用一个二元分类器。也许您的文档中有一些非序数(数字)特征 - 您需要适当地将它们分组。 - ephes
你使用了哪个修改版的liblinear?或者说你们自己做了哪些修改? - Benedikt Waldvogel
建议将精确率/召回率作为分类器质量的度量标准,+1。 - digitalarbeiter
在测量性能方面,绝对召回率/精度/F-度量是非常标准的信息学领域。建议使用k折交叉验证进行测量。同时,我也同意,在进行二元分类(是X还是不是X)时,性能会更好,而不是尝试一次性标记所有内容。 - Thien
我理解的是,对于多标签分类,一个好的解决方案是针对所有标签进行二元分类,而不是在一次遍历中找到最相关的标签。 - philgo20

2
有没有一种方法可以为分类器添加“无法归类”的选项,以防文档不适合任何类别?
您可以通过每次训练一个“无法归类”的伪类别来实现这种效果。如果您最多只能训练5个类别(虽然我不确定为什么会占用如此多的RAM),则从实际的2K文档中训练4个实际类别,并从其他146个类别中随机选择其2K文档作为“无法归类”的文档(如果您想要“分层抽样”方法,则每个类别大约需要13-14个)。
但这仍然感觉有点笨拙,您可能最好采用完全不同的方法--找到一个多维文档度量标准,将您的300K预标记文档定义为150个合理可分离的聚类,然后根据确定的聚类将每个未标记的文档分配给相应的聚类。我不认为NLTK有直接支持这种事情的东西,但是,嘿,NLTK增长得如此之快,我可能错过了一些东西...;-)

我们有一类特殊的文档,我们知道无法正确地加以分类。虽然有些不太优雅,但它确实运作得很好。 - ephes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接