为CountVectorizer(sklearn)添加词干支持。

25

我正在尝试在sklearn中的NLP管道中添加词干处理。

from nltk.stem.snowball import FrenchStemmer

stop = stopwords.words('french')
stemmer = FrenchStemmer()


class StemmedCountVectorizer(CountVectorizer):
    def __init__(self, stemmer):
        super(StemmedCountVectorizer, self).__init__()
        self.stemmer = stemmer

    def build_analyzer(self):
        analyzer = super(StemmedCountVectorizer, self).build_analyzer()
        return lambda doc:(self.stemmer.stem(w) for w in analyzer(doc))

stem_vectorizer = StemmedCountVectorizer(stemmer)
text_clf = Pipeline([('vect', stem_vectorizer), ('tfidf', TfidfTransformer()), ('clf', SVC(kernel='linear', C=1)) ])
当使用此管道与sklearn的CountVectorizer时,它可以工作。如果我像这样手动创建特征,它也可以工作。
vectorizer = StemmedCountVectorizer(stemmer)
vectorizer.fit_transform(X)
tfidf_transformer = TfidfTransformer()
X_tfidf = tfidf_transformer.fit_transform(X_counts)

编辑:

如果我在我的IPython笔记本上尝试这个流程,它会显示[*]并且什么都不会发生。当我查看我的终端时,它会给出以下错误:

Process PoolWorker-12:
Traceback (most recent call last):
  File "C:\Anaconda2\lib\multiprocessing\process.py", line 258, in _bootstrap
    self.run()
  File "C:\Anaconda2\lib\multiprocessing\process.py", line 114, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Anaconda2\lib\multiprocessing\pool.py", line 102, in worker
    task = get()
  File "C:\Anaconda2\lib\site-packages\sklearn\externals\joblib\pool.py", line 360, in get
    return recv()
AttributeError: 'module' object has no attribute 'StemmedCountVectorizer'

例子

以下是完整的示例

from sklearn.pipeline import Pipeline
from sklearn import grid_search
from sklearn.svm import SVC
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from nltk.stem.snowball import FrenchStemmer

stemmer = FrenchStemmer()
analyzer = CountVectorizer().build_analyzer()

def stemming(doc):
    return (stemmer.stem(w) for w in analyzer(doc))

X = ['le chat est beau', 'le ciel est nuageux', 'les gens sont gentils', 'Paris est magique', 'Marseille est tragique', 'JCVD est fou']
Y = [1,0,1,1,0,0]

text_clf = Pipeline([('vect', CountVectorizer()), ('tfidf', TfidfTransformer()), ('clf', SVC())])
parameters = { 'vect__analyzer': ['word', stemming]}

gs_clf = grid_search.GridSearchCV(text_clf, parameters, n_jobs=-1)
gs_clf.fit(X, Y)

如果从参数中删除词干处理,它就有效,否则它无法工作。

更新:

问题似乎在并行化过程中,因为当删除 n_jobs=-1 时,问题消失了。


这似乎是与腌制和解腌作用域有关的问题。例如,如果您将stemming放在一个导入的模块中,它将更可靠地被解腌。 - joeln
请您提供一个示例或链接以便理解您所说的内容吗?如何将“stemming”放入导入的模块中?因为没有并行化,对于需要调整的少量参数,GridSearch速度相当慢。 - dooms
就此而言,我可以毫无问题地运行您的完整示例。但我的意思是将“stemming”的代码移动到“myutils.py”中,并使用“from myutils import stemming”。 - joeln
是的,终于成功了。你能否编辑你的答案,这样我就可以接受它了吗?因为这确实是我的问题。 - dooms
您能先澄清一下您运行代码的方式吗?比如是在交互式控制台、IDLE、Jupyter notebook、脚本文件等等中输入运行的,还是其他方式?因为这样更容易分析问题所在。 - joeln
3个回答

34

您可以将可调用对象作为 CountVectorizer 构造函数的 analyzer 参数,以提供自定义分析器。这对我来说似乎是有效的。

from sklearn.feature_extraction.text import CountVectorizer
from nltk.stem.snowball import FrenchStemmer

stemmer = FrenchStemmer()
analyzer = CountVectorizer().build_analyzer()

def stemmed_words(doc):
    return (stemmer.stem(w) for w in analyzer(doc))

stem_vectorizer = CountVectorizer(analyzer=stemmed_words)
print(stem_vectorizer.fit_transform(['Tu marches dans la rue']))
print(stem_vectorizer.get_feature_names())

输出:

  (0, 4)    1
  (0, 2)    1
  (0, 0)    1
  (0, 1)    1
  (0, 3)    1
[u'dan', u'la', u'march', u'ru', u'tu']

参数 = {'vect__analyzer': ['word', 'stemming']} 将其用作参数进行网格搜索会出现错误: AttributeError: 'module' object has no attribute 'stemming' - dooms
如果我们覆盖了分析器参数并且它不再是“word”的默认值,那么文档中是否禁用了分词器和停用词参数:https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html - James Carron
在什么情况下需要在同一个分析器函数中实现它? - James Carron

20

我知道我回答有点晚了,但如果还有人需要帮助的话,这是我的答案。

以下是最干净的方法,通过覆盖build_analyser()来将语言分词器添加到计数向量化器中。

from sklearn.feature_extraction.text import CountVectorizer
import nltk.stem

french_stemmer = nltk.stem.SnowballStemmer('french')
class StemmedCountVectorizer(CountVectorizer):
    def build_analyzer(self):
        analyzer = super(StemmedCountVectorizer, self).build_analyzer()
        return lambda doc: ([french_stemmer.stem(w) for w in analyzer(doc)])

vectorizer_s = StemmedCountVectorizer(min_df=3, analyzer="word", stop_words='french')

您可以自由地在vectorizer_s对象上调用CountVectorizer类的fittransform函数。


谢谢您提供的代码。我运行了这段代码,词干处理器工作正常,但是在stop_words参数下提供的自定义停用词不再起作用了。有什么解决方法吗? - Ramya
@Ramya 是的,有一个解决方法:从nltk.corpus导入停用词,StemmedCountVectorizer(..., stop_words=stopwords.words('french'))。 - Chiraz BenAbdelkader
1
@ChirazBenAbdelkader 这不会去除停用词。正如文档所述stop_words参数仅适用于analyzer == word - robertspierre
你应该在传递停用词之前对它们进行词干处理吗?我的意思是,在分析器应用之前还是之后过滤停用词? - rubencart
我想我找到了它(如果我错了请纠正):代码链接,当通过覆盖build_analyzer添加时,停用词移除后才进行词干提取,因此对停用词进行词干提取是没有意义的。 - rubencart
一个相关的问题是,是否在词干提取之后进行停用词去除更有意义?在这种情况下,像本文所建议的那样添加词干提取可能不是最好的方法。 - rubencart

1
你可以尝试:

def build_analyzer(self):
    analyzer = super(CountVectorizer, self).build_analyzer()
    return lambda doc:(stemmer.stem(w) for w in analyzer(doc))

并且删除__init__方法。

它不起作用(给出相同的错误),我需要stemmer属性。 - dooms
你能提供更多关于错误的信息吗?比如哪一行出现了错误? - Till
我正在使用n_jobs=-1的GridSearch来并行化工作。 - dooms
问题可能是由于lambda函数无法被pickle化。只需用def函数替换lambda函数即可。 - Ricardo Magalhães Cruz

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