NLTK 标题分类器

Posted

技术标签:

【中文标题】NLTK 标题分类器【英文标题】:NLTK title classifier 【发布时间】:2018-04-09 10:43:13 【问题描述】:

如果这已经被质疑/回答,请提前道歉,但我找不到任何接近我的问题的答案。我在处理 Python 方面也有些菜鸟,对于这篇长篇文章也很抱歉。

我正在尝试构建一个 Python 脚本,该脚本基于用户给定的 Pubmed 查询(即“癌症”),检索具有 N 个文章标题的文件,并评估它们与相关主题的相关性。

我已经成功构建了“pubmed 搜索和保存”部分,让它返回一个包含文章标题的 .txt 文件(每行对应一个不同的文章标题),例如:

卵巢癌生活质量心理教育干预的可行性。

一项增加乳腺癌幸存者体力活动的随机试验。

有了这个文件,想法是将它用于分类器并让它回答 .txt 文件中的标题是否与某个主题相关,对此我有一个我知道的标题的“黄金标准”相关的(即,我想根据我的黄金标准了解所查询的标题集的精确度和召回率)。例如:标题 1 有 X 次“neoplasm”和“study”N 次,因此它被认为与“cancer”相关(Y/N)。

为此,我一直在使用 NLTK 来(尝试)对我的文本进行分类。我采用了两种不同的方法,均未成功:

方法 1

加载 .txt 文件,对其进行预处理(标记化、小写、删除停用词),将文本转换为 NLTK 文本格式,找到 N 个最常用的词。所有这些运行都没有问题。

f = open('SR_titles.txt')
raw = f.read() 
tokens = word_tokenize(raw)
words = [w.lower() for w in tokens]
words = [w for w in words if not w in stopwords.words("english")]
text = nltk.Text(words)
fdist = FreqDist(text)
>>><FreqDist with 116 samples and 304 outcomes>

我还能够在文本中找到 colocations/bigrams,这在之后可能很重要。

text.collocations()
>>>randomized controlled; breast cancer; controlled trial; physical
>>>activity; metastatic breast; prostate cancer; randomised study; early
>>>breast; cancer patients; feasibility study; psychosocial support;
>>>group psychosocial; group intervention; randomized trial

按照NLTKs tutorial,我构建了一个特征提取器,这样分类器就会知道它应该注意数据的哪些方面。

def document_features(document):
  document_words = set(document)
  features = 
  for word in word_features:
      features['contains()'.format(word)] = (word in document_words)
  return features

例如,这将返回如下内容:

'contains(series)': False, 'contains(disorders)': False,
'contains(group)': True, 'contains(neurodegeneration)': False,
'contains(human)': False, 'contains(breast)': True

接下来是使用特征提取器训练分类器来标记新文章标题,并按照 NLTKs 示例,我尝试了这个:

featuresets = [(document_features(d), c) for (d,c) in text]

这给了我错误:

ValueError: too many values to unpack

快速搜索了一下,发现这与元组有关,但不知道如何解决它(就像我说的,我对此有点菜鸟),除非通过创建分类语料库(我仍然会想了解如何解决这个元组问题)。

因此,我尝试了方法 2,遵循 Jacob Perkings Text Processing with NLTK Cookbook:

从创建语料库和归属类别开始。这一次我有 2 个不同的 .txt 文件,一个用于标题文章的每个主题。

reader = CategorizedPlaintextCorpusReader('.', r'.*\,
    cat_map='hd_titles.txt': ['HD'], 'SR_titles.txt': ['Cancer'])

使用“reader.raw()”我会得到这样的结果:

u“一项针对前列腺癌生化复发男性的多学科生活质量干预的试点调查。\n一项针对乳腺癌综合支持计划的生理和心理影响的随机对照试点可行性研究。\n”

语料库的类别似乎是正确的:

reader.categories()
>>>['Cancer', 'HD']

然后,我尝试构建一个文档列表,并标有适当的类别:

documents = [(list(reader.words(fileid)), category)
          for category in reader.categories()
          for fileid in reader.fileids(category)]

返回给我的是这样的:

[([u'A', u'pilot', u'investigation', u'of', u'a', u'multidisciplinary',
u'quality', u'of', u'life', u'intervention', u'for', u'men', u'with', 
u'biochemical', u'recurrence', u'of', u'prostate', u'cancer', u'.'], 
'Cancer'), 
 ([u'Trends', u'in', u'the', u'incidence', u'of', u'dementia', u':', 
u'design', u'and', u'methods', u'in', u'the', u'Alzheimer', u'Cohorts', 
u'Consortium', u'.'], 'HD')]

下一步将创建一个标记的特征集列表,为此我使用了下一个函数,它需要一个语料库和一个 feature_detector 函数(即上面提到的 document_features)。然后它构造并返回 label: [featureset] 形式的映射。

def label_feats_from_corpus(corp, feature_detector=document_features):
    label_feats = collections.defaultdict(list)
    for label in corp.categories():
        for fileid in corp.fileids(categories=[label]):
            feats = feature_detector(corp.words(fileids=[fileid]))
            label_feats[label].append(feats)
    return label_feats 

lfeats = label_feats_from_corpus(reader)
>>>defaultdict(<type 'list'>, 'HD': ['contains(series)': True, 
'contains(disorders)': True, 'contains(neurodegeneration)': True, 
'contains(anilinoquinazoline)': True], 'Cancer': ['contains(cancer)': 
True, 'contains(of)': True, 'contains(group)': True, 'contains(After)': 
True, 'contains(breast)': True])

(列表要大得多,所有内容都设置为 True)。

然后我想构造一个带标签的训练实例和测试实例的列表。

split_label_feats() 函数采用从返回的映射 label_feats_from_corpus() 并拆分每个特征集列表 进入标记的训练和测试实例。

def split_label_feats(lfeats, split=0.75):
    train_feats = []
    test_feats = []
    for label, feats in lfeats.items():
        cutoff = int(len(feats) * split)
        train_feats.extend([(feat, label) for feat in feats[:cutoff]])
        test_feats.extend([(feat, label) for feat in feats[cutoff:]])
    return train_feats, test_feats

train_feats, test_feats = split_label_feats(lfeats, split=0.75)
len(train_feats)
>>>0
len(test_feats)
>>>2
print(test_feats)
>>>[('contains(series)': True, 'contains(China)': True, 
'contains(disorders)': True, 'contains(neurodegeneration)': True, 
'HD'), ('contains(cancer)': True, 'contains(of)': True, 
'contains(group)': True, 'contains(After)': True, 'contains(breast)': 
True, 'Cancer')]

我猜,我应该得到更多带标签的训练实例和带标签的测试实例。

这将我带到了现在的位置。我搜索了 ***、biostars 等,但找不到如何处理这两个问题,所以任何帮助将不胜感激。

TL;DR:无法标记单个 .txt 文件以对文本进行分类,也无法获得正确标记的语料库(再次,对文本进行分类)。

如果你已经读到这里,也谢谢你。

【问题讨论】:

【参考方案1】:

featuresets = [(document_features(d), c) for (d,c) in text],我不确定你应该从text 得到什么。 text 似乎是一个 nltk 类,它只是一个生成器的包装器。似乎每次迭代都会给你一个字符串,这就是为什么你会得到一个错误,因为你要求两个项目,而它只有一个可以给出。

【讨论】:

@alexis 指出了它的正确性,它应该得到类似 [('contains(cancer)': True, (...), 'contains(usual)': True, 'Cancer '), ('contains(neurodegeneration)': True, (...), 'contains(neurodegeneration)': True, 'HD')]。问题是文本是 NLTK 格式,但没有类别,即该代码工作所需的元组(这就是我遵循方法 2 的原因)【参考方案2】:

您在以下行收到错误:

featuresets = [(document_features(d), c) for (d,c) in text]

在这里,您应该将每个文档(即每个标题)转换为特征字典。但是要使用结果进行训练,train() 方法需要特征字典和正确答案(“标签”)。所以正常的工作流程是有一个(document, label) 对的列表,您可以将其转换为(features, label) 对。看起来你的变量 documents 具有正确的结构,所以如果你只使用它而不是 text,这应该可以正常工作:

featuresets = [(document_features(d), c) for (d,c) in documents]

在您前进的过程中,养成仔细检查您的数据并弄清楚它们将会(和应该)发生什么的习惯。如果text 是一个标题列表,那么将每个标题解压缩成一对(d, c) 是没有意义的。那应该为您指明了正确的方向。

【讨论】:

你说得对,这行得通 - 老实说,我意识到我在方法 2 的某个地方做了这个作为测试,但没有遵循它。方法 1 的问题是我没有“文档”变量,它是由 CategorizedPlaintextCorpusReader 在方法 2 上创建的。我无法在应用程序上创建它。 1 因为我没有为标题设置类别,它们只是纯文本。 我不明白。是什么阻止您从方法 2 中获取 documents 变量并将其与方法 1 一起使用,正如我在回答中建议的那样 什么都没有,但由于我仍在努力了解下一步该做什么,我只是在探索所有可能性。但你是对的,是的,这是正确的方法,也是我现在使用的方法。谢谢你:)

以上是关于NLTK 标题分类器的主要内容,如果未能解决你的问题,请参考以下文章

推文分类器特征选择 NLTK

Python NLTK 最大熵分类器错误

用于整数特征的 NLTK 分类器?

NLTK 标题分类器

为啥 NLTK NaiveBayes 分类器错误分类了一条记录?

NLTK 的朴素贝叶斯分类器是不是适合商业应用?