使用Pipeline和GridSearch执行功能选择



作为研究项目的一部分,我想选择最佳组合预处理技术和文本功能,以优化文本分类任务的结果。为此,我正在使用Python 3.6。

有许多方法可以结合功能和算法,但是我想充分利用Sklearn的管道并使用网格搜索来测试所有不同的(有效)可能性的可能性。

我的第一步是构建一个看起来如下的管道:

# Run a vectorizer with a predefined tweet tokenizer and a Naive Bayes
pipeline = Pipeline([
    ('vectorizer', CountVectorizer(tokenizer = tweet_tokenizer)),
    ('nb', MultinomialNB())
])
parameters = {
'vectorizer__preprocessor': (None, preprocessor)
}
gs =  GridSearchCV(pipeline, parameters, cv=5, n_jobs=-1, verbose=1)

在这个简单的示例中,矢量机使用Tweet_tokenizer对数据进行象征,然后测试更好的预处理选项(无或预定函数)结果。

这似乎是一个不错的开始,但是我现在正在努力寻找一种测试预处理器功能中所有不同可能性的方法,以下是:

def preprocessor(tweet):
    # Data cleaning
    tweet = URL_remover(tweet) # Removing URLs
    tweet = mentions_remover(tweet) # Removing mentions
    tweet = email_remover(tweet) # Removing emails
    tweet = irrelev_chars_remover(tweet) # Removing invalid chars
    tweet = emojies_converter(tweet) # Translating emojies
    tweet = to_lowercase(tweet) # Converting words to lowercase
    # Others
    tweet = hashtag_decomposer(tweet) # Hashtag decomposition
    # Punctuation may only be removed after hashtag decomposition  
    # because it considers "#" as punctuation
    tweet = punct_remover(tweet) # Punctuation 
    return tweet

一个"简单"解决方案,用于组合所有不同的处理技术将是为每种可能性创建一个不同的功能(例如FUNCA:PROC1,FUNCB:PROC1 PROC2,FUNCC:PROC1 PROC3等),并设置网格参数如下:

parameters = {
   'vectorizer__preprocessor': (None, funcA, funcB, funcC, ...)
}

尽管这很可能会起作用,但对于此任务而言,这不是一个可行或合理的解决方案,尤其是因为2^n_features有不同的组合,因此功能。

最终目标是在管道中将两种预处理技术和功能结合在一起,以便使用GridSearch优化分类结果:

pipeline = Pipeline([
    ('vectorizer', CountVectorizer(tokenizer = tweet_tokenizer)),
    ('feat_extractor' , feat_extractor)
    ('nb', MultinomialNB())
])
 parameters = {
   'vectorizer__preprocessor': (None, funcA, funcB, funcC, ...)
   'feat_extractor': (None, func_A, func_B, func_C, ...)
 }

是否有一种更简单的方法来获得此?

此解决方案非常粗糙,根据您的描述,并根据所使用的数据类型而具有特定答案。在制作管道之前,让我们了解CountVectorizer在其中传递的raw_documents上的工作方式。本质上,这是将字符串文档处理到令牌中的行,

return lambda doc: self._word_ngrams(tokenize(preprocess(self.decode(doc))), stop_words)

然后仅计数并转换为计数矩阵。

所以这里发生的是:

  1. decode:只需决定如何从文件中读取数据(如果指定)。对我们不使用,我们已经将数据列入列表。
  2. preprocess:如果'strip_accents''lowercase'True,则可以执行以下操作。否则什么都没有

    strip_accents(x.lower())
    

    再次没有用,因为我们将小写功能移至我们自己的预处理器,而不需要剥离口音,因为我们已经在字符串列表中有数据。

  3. tokenize:将删除所有标点,仅保留长度为2或更多的字母数字单词,然后返回单个文档(列表的元素)的令牌列表

    lambda doc: token_pattern.findall(doc)
    

    应牢记这一点。如果您想自己处理标点符号和其他符号(决定保留某些符号并删除其他符号),那么也更好地更改CountVectorizer的默认token_pattern=’(?u)bww+b’

    1. _word_ngrams:此方法将首先从上一步的令牌列表中删除停止单词(如上所述),然后计算CountVectorizerngram_range参数定义的N_Grams。如果您想以自己的方式处理"n_grams",也应该牢记这一点。

NOTE :如果将分析器设置为'char',则将不执行tokenizer步骤,并且将通过字符制作N_Grams。

所以现在来我们的管道。这是我认为可以在这里工作的结构:

X --> combined_pipeline, Pipeline
            |
            |  Raw data is passed to Preprocessor
            |
            /
         Preprocessor 
                 |
                 |  Cleaned data (still raw texts) is passed to FeatureUnion
                 |
                 /
              FeatureUnion
                      |
                      |  Data is duplicated and passed to both parts
       _______________|__________________
      |                                  |
      |                                  |                         
      /                                /
   CountVectorizer                  FeatureExtractor
           |                                  |   
           |   Converts raw to                |   Extracts numerical features
           |   count-matrix                   |   from raw data
           /________________________________/
                             |
                             | FeatureUnion combines both the matrices
                             |
                             /
                          Classifier

现在来代码。这就是管道的样子:

# Imports
from sklearn.svm import SVC
from sklearn.pipeline import FeatureUnion, Pipeline
# Pipeline
pipe = Pipeline([('preprocessor', CustomPreprocessor()), 
                 ('features', FeatureUnion([("vectorizer", CountVectorizer()),
                                            ("extractor", CustomFeatureExtractor())
                                            ]))
                 ('classifier', SVC())
                ])

其中CustomPreprocessorCustomFeatureExtractor定义为:

from sklearn.base import TransformerMixin, BaseEstimator
class CustomPreprocessor(BaseEstimator, TransformerMixin):
    def __init__(self, remove_urls=True, remove_mentions=True, 
                 remove_emails=True, remove_invalid_chars=True, 
                 convert_emojis=True, lowercase=True, 
                 decompose_hashtags=True, remove_punctuations=True):
        self.remove_urls=remove_urls
        self.remove_mentions=remove_mentions
        self.remove_emails=remove_emails
        self.remove_invalid_chars=remove_invalid_chars
        self.convert_emojis=convert_emojis
        self.lowercase=lowercase
        self.decompose_hashtags=decompose_hashtags
        self.remove_punctuations=remove_punctuations
    # You Need to have all the functions ready
    # This method works on single tweets
    def preprocessor(self, tweet):
        # Data cleaning
        if self.remove_urls:
            tweet = URL_remover(tweet) # Removing URLs
        if self.remove_mentions:
            tweet = mentions_remover(tweet) # Removing mentions
        if self.remove_emails:
            tweet = email_remover(tweet) # Removing emails
        if self.remove_invalid_chars:
            tweet = irrelev_chars_remover(tweet) # Removing invalid chars
        if self.convert_emojis:
            tweet = emojies_converter(tweet) # Translating emojies
        if self.lowercase:
            tweet = to_lowercase(tweet) # Converting words to lowercase
        if self.decompose_hashtags:
            # Others
            tweet = hashtag_decomposer(tweet) # Hashtag decomposition
        # Punctuation may only be removed after hashtag decomposition  
        # because it considers "#" as punctuation
        if self.remove_punctuations:
            tweet = punct_remover(tweet) # Punctuation 
        return tweet
    def fit(self, raw_docs, y=None):
        # Noop - We dont learn anything about the data
        return self
    def transform(self, raw_docs):
        return [self.preprocessor(tweet) for tweet in raw_docs]
from textblob import TextBlob
import numpy as np
# Same thing for feature extraction
class CustomFeatureExtractor(BaseEstimator, TransformerMixin):
    def __init__(self, sentiment_analysis=True, tweet_length=True):
        self.sentiment_analysis=sentiment_analysis
        self.tweet_length=tweet_length
    # This method works on single tweets
    def extractor(self, tweet):
        features = []
        if self.sentiment_analysis:
            blob = TextBlob(tweet)
            features.append(blob.sentiment.polarity)
        if self.tweet_length:
            features.append(len(tweet))
        # Do for other features you want.
        return np.array(features)
    def fit(self, raw_docs, y):
        # Noop - Again I am assuming that We dont learn anything about the data
        # Definitely not for tweet length, and also not for sentiment analysis
        # Or any other thing you might have here.
        return self
    def transform(self, raw_docs):
        # I am returning a numpy array so that the FeatureUnion can handle that correctly
        return np.vstack(tuple([self.extractor(tweet) for tweet in raw_docs]))

最后,现在可以轻松完成参数网格:

param_grid = ['preprocessor__remove_urls':[True, False],
              'preprocessor__remove_mentions':[True, False],
              ...
              ...
              # No need to search for lowercase or preprocessor in CountVectorizer 
              'features__vectorizer__max_df':[0.1, 0.2, 0.3],
              ...
              ...
              'features__extractor__sentiment_analysis':[True, False],
              'features__extractor__tweet_length':[True, False],
              ...
              ...
              'classifier__C':[0.01, 0.1, 1.0]
            ]

上面的代码是避免" to create a different function for each possibility (e.g. funcA: proc1, funcB: proc1 + proc2, funcC: proc1 + proc3, etc.)"。只要做真实,错误和GridSearchCV就可以处理。

更新:如果您不想拥有CountVectorizer,则可以从管道和参数网格中删除该CC_22,而新管道将是:

pipe = Pipeline([('preprocessor', CustomPreprocessor()), 
                 ("extractor", CustomFeatureExtractor()),
                 ('classifier', SVC())
                ])

然后确保在CustomFeatureExtractor中实现所需的所有功能。如果那变得太复杂了,那么您始终可以制作更简单的提取器并将它们组合在一起,以代替CountVectorizer

相关内容

  • 没有找到相关文章

最新更新