使用NLTK和Scikit-Learn做文本分类【翻译】
原文:http://bbengfort.github.io/tutorials/2016/05/19/text-classification-nltk-sckit-learn.html
作者经常被问到在文本处理中,NLTK或者Scikit-Learn哪一个更好用。作者的回答是他经常混合着使用这些工具。在这篇博客中,作者主要讲述如何使用NLTK来做预处理和序列化和词语切分,然后使用Scikit-Learn处理机器学习任务(如建立线性SVM方法,用梯度下降求解)。作者将描述如何使用NLTK处理一个对电影评论做文本分类的任务。这里,电影评论将被分为正向和负向。
首先请确保本机安装了NLTK和Scikit-Learn。
pip install nltk scikit-learn
python -m nltk.downloader all
同时,作者也会使用一些辅助工具,如timeit、identity等。完整的代码请见sentiment.py。注意,代码中去除了import部分。
Pipelines
使用Scikit-Learn构建机器学习工具的最核心部分是Pipelines。Scikit-Learn给出的关于机器学习中标准API主要由两个接口:Transformer和Estimator。这两个类都有一个fit方法,其作用是基于数据优化内部参数。Transformers然后实现了一个transform方法,用来进行特征抽取或者修改数据,estimators实现了预测方法,用来从特征向量中产生新数据。
Pipelines允许开发者构建一个有序的有向无环图的transformers,并带有一个estimator,来确保特征抽取与预测过程紧密相联。这在文本处理中特别重要,通常这些原始文本是在硬盘上的文档。尽管Scikit-Learn提供了文本处理的相关接口,但是NLTK却是一个更好的自然语言处理的工具。作者个人的文本处理过程通常是如下顺序:

CorpusReader从原始语料中读取数据。Tokenizer将原始文本切分成句子,单词和标点符号。然后使用WordNet词典进行词性标注(part of speech, POS)与词干抽取(lemmatization)。Vectorizer则将文本中的单词进行编码(比如使用TF-IDF)成向量。最终就可以使用分类器训练了。
预处理
为了限制特征的长度,并提供一个高质量的文本表示,作者使用NLTK的高级文本处理机制,包括Punkt切词,Brill标注以及基于WordNet词典的词干抽取。这些操作不仅可以减少词汇,也可以将冗余单词变成一个(比如,bunny,bunnies,Bunny,bunny!,和d _bunny_都可以变成bunny)。
为了把这样的预处理过程加入到Scikit-Learn的处理中,我们创建一个Transformer对象:
import string
from nltk.corpus import stopwords as sw
from nltk.corpus import wordnet as wn
from nltk import wordpunct_tokenize
from nltk import WordNetLemmatizer
from nltk import sent_tokenize
from nltk import pos_tag
from sklearn.base import BaseEstimator, TransformerMixin
class NLTKPreprocessor(BaseEstimator, TransformerMixin):
def __init__(self, stopwords=None, punct=None,
lower=True, strip=True):
self.lower = lower
self.strip = strip
self.stopwords = stopwords or set(sw.words('english'))
self.punct = punct or set(string.punctuation)
self.lemmatizer = WordNetLemmatizer()
def fit(self, X, y=None):
return self
def inverse_transform(self, X):
return [" ".join(doc) for doc in X]
def transform(self, X):
return [
list(self.tokenize(doc)) for doc in X
]
def tokenize(self, document):
# Break the document into sentences
for sent in sent_tokenize(document):
# Break the sentence into part of speech tagged tokens
for token, tag in pos_tag(wordpunct_tokenize(sent)):
# Apply preprocessing to the token
token = token.lower() if self.lower else token
token = token.strip() if self.strip else token
token = token.strip('_') if self.strip else token
token = token.strip('*') if self.strip else token
# If stopword, ignore token and continue
if token in self.stopwords:
continue
# If punctuation, ignore token and continue
if all(char in self.punct for char in token):
continue
# Lemmatize the token and yield
lemma = self.lemmatize(token, tag)
yield lemma
def lemmatize(self, token, tag):
tag = {
'N': wn.NOUN,
'V': wn.VERB,
'R': wn.ADV,
'J': wn.ADJ
}.get(tag[0], wn.NOUN)
return self.lemmatizer.lemmatize(token, tag)
这是一大块代码,我们将一个方法一个方法的分析。首先初始化这个Transformer,它可以载入各种语料并进行切分。通过使用NLTK内置的停用词,WordNetLemmatizer将从WordNet字典进行查询。这里可能会需要花点时间。
