spaCy 和 scikit-learn 向量化器

8

我使用spaCy为scikit-learn编写了一个引理分词器,基于他们的示例,它可以独立运行,效果还不错:

import spacy
from sklearn.feature_extraction.text import TfidfVectorizer

class LemmaTokenizer(object):
    def __init__(self):
        self.spacynlp = spacy.load('en')
    def __call__(self, doc):
        nlpdoc = self.spacynlp(doc)
        nlpdoc = [token.lemma_ for token in nlpdoc if (len(token.lemma_) > 1) or (token.lemma_.isalnum()) ]
        return nlpdoc

vect = TfidfVectorizer(tokenizer=LemmaTokenizer())
vect.fit(['Apples and oranges are tasty.'])
print(vect.vocabulary_)
### prints {'apple': 1, 'and': 0, 'tasty': 4, 'be': 2, 'orange': 3}

然而,在使用GridSearchCV时会出现错误,下面是一个自包含的示例:

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier
from sklearn.pipeline import Pipeline
from sklearn.grid_search import GridSearchCV

wordvect = TfidfVectorizer(analyzer='word', strip_accents='ascii', tokenizer=LemmaTokenizer())
classifier = OneVsRestClassifier(SVC(kernel='linear'))
pipeline = Pipeline([('vect', wordvect), ('classifier', classifier)])
parameters = {'vect__min_df': [1, 2], 'vect__max_df': [0.7, 0.8], 'classifier__estimator__C': [0.1, 1, 10]}
gs_clf = GridSearchCV(pipeline, parameters, n_jobs=7, verbose=1)

from sklearn.datasets import fetch_20newsgroups
categories = ['comp.graphics', 'rec.sport.baseball']
newsgroups = fetch_20newsgroups(remove=('headers', 'footers', 'quotes'), shuffle=True, categories=categories)
X = newsgroups.data
y = newsgroups.target
gs_clf = gs_clf.fit(X, y)

### AttributeError: 'spacy.tokenizer.Tokenizer' object has no attribute '_prefix_re'

当我在分词器的构造函数之外加载spacy时,错误不会出现,然后GridSearchCV运行:
spacynlp = spacy.load('en')
    class LemmaTokenizer(object):
        def __call__(self, doc):
            nlpdoc = spacynlp(doc)
            nlpdoc = [token.lemma_ for token in nlpdoc if (len(token.lemma_) > 1) or (token.lemma_.isalnum()) ]
            return nlpdoc

但这意味着我的每个n_jobs都将访问并调用同一个spacynlp对象,它在这些作业之间共享,这引出了以下问题:
  1. spacy.load('en')的spacynlp对象是否安全可供GridSearchCV中的多个作业使用?
  2. 这是在scikit-learn的分词器中实现对spacy的调用的正确方法吗?
2个回答

2
根据mbatchkarov的帖子评论,我尝试通过Spacy对我的所有文档进行分词和词形还原,并将其保存到磁盘中。 然后,我加载了词形还原后的Spacy Doc对象,为每个文档提取一个标记列表,并将其作为输入提供给由简化的TfidfVectorizerDecisionTreeClassifier组成的管道。 我使用GridSearchCV运行pipeline并提取最佳估计器和相应参数。

以下是一个例子:

from sklearn import tree
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
import spacy
from spacy.tokens import DocBin
nlp = spacy.load("de_core_news_sm") # define your language model

# adjust attributes to your liking:
doc_bin = DocBin(attrs=["LEMMA", "ENT_IOB", "ENT_TYPE"], store_user_data=True)

for doc in nlp.pipe(df['articleDocument'].str.lower()):
    doc_bin.add(doc)

# either save DocBin to a bytes object, or...
#bytes_data = doc_bin.to_bytes()

# save DocBin to a file on disc
file_name_spacy = 'output/preprocessed_documents.spacy'
doc_bin.to_disk(file_name_spacy)

#Load DocBin at later time or on different system from disc or bytes object
#doc_bin = DocBin().from_bytes(bytes_data)
doc_bin = DocBin().from_disk(file_name_spacy)

docs = list(doc_bin.get_docs(nlp.vocab))
print(len(docs))

tokenized_lemmatized_texts = [[token.lemma_ for token in doc 
                               if not token.is_stop and not token.is_punct and not token.is_space and not token.like_url and not token.like_email] 
                               for doc in docs]

# classifier to use
clf = tree.DecisionTreeClassifier()

# just some random target response
y = np.random.randint(2, size=len(docs))


vectorizer = TfidfVectorizer(ngram_range=(1, 1), lowercase=False, tokenizer=lambda x: x, max_features=3000)

pipeline = Pipeline([('vect', vectorizer), ('dectree', clf)])
parameters = {'dectree__max_depth':[4, 10]}
gs_clf = GridSearchCV(pipeline, parameters, n_jobs=-1, verbose=1, cv=5)
gs_clf.fit(tokenized_lemmatized_texts, y)
print(gs_clf.best_estimator_.get_params()['dectree'])

以下是相关的一些有用资源:

0

在网格的每个参数设置中运行Spacy是浪费时间。内存开销也很大。您应该将所有数据通过Spacy运行一次并保存到磁盘,然后使用简化的向量化器读取预先词形还原的数据。查看TfidfVectorizertokenizeranalyserpreprocessor参数。有很多在stackoverflow上的示例展示了如何构建自定义向量化器。


这些是很好的观点,这可能很有可能是要做的事情。然而,我最终希望将spaCy标记化与不同选项(如POS)作为超参数网格搜索的一部分,因此我的问题。 - tkja
你也可以这样做。将你的数据存储为字典列表,格式如下:[{"token": "cats", "lemma": "cat"}, {...}]。这基本上就是Spacy句子转换为JSON的方式。编写一个管道步骤,以此作为输入,并具有参数来输出令牌或词形,这样你就拥有了令牌化作为网格搜索的一部分。 - mbatchkarov
12
你在浪费时间。有很多例子可供参考。这个回答并不是非常有用。 - astrojuanlu
欢迎提出改进意见。编辑按钮位于帖子底部。 - mbatchkarov
4
有很多关于如何构建自定义向量化器的示例可以在 Stack Overflow 上找到,将其中至少一个示例链接会非常有帮助。 - Zach

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