Выполните выбор объектов, используя конвейер и gridsearch - PullRequest
0 голосов
/ 19 декабря 2018

В рамках исследовательского проекта я хочу выбрать комбинацию best методов предварительной обработки и текстовых функций, которые оптимизируют результаты задачи классификации текста.Для этого я использую 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, ...)
 }

Есть ли более простой способ получить это?

1 Ответ

0 голосов
/ 19 декабря 2018

Это решение очень грубое на основе вашего описания и зависит от ответа в зависимости от типа используемых данных.Прежде чем приступить к конвейеру, давайте разберемся, как CountVectorizer работает с raw_documents, которые передаются в нем.По сути, это строка , которая обрабатывает строковые документы в токены,

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

, которые затем просто подсчитываются и преобразуются в матрицу подсчета.

Так что здесь происходитis:

  1. decode: Просто решите, как читать данные из файла (если указан).Не пригодится нам, когда у нас уже есть данные в списке.
  2. preprocess: Это делает следующее, если 'strip_accents' и 'lowercase' равны True в CountVectorizer.Иначе ничего

    strip_accents(x.lower())
    

    Опять же, бесполезно, потому что мы перемещаем функциональность в нижнем регистре на наш собственный препроцессор и нам не нужно удалять акценты, потому что у нас уже есть данные в списке строк.

  3. tokenize: удалит все знаки препинания и сохранит только буквенно-цифровые слова длиной 2 или более и вернет список токенов для одного документа (элемент списка)

    lambda doc: token_pattern.findall(doc)
    

    Это должно бытьпомнить.Если вы хотите обрабатывать знаки препинания и другие символы самостоятельно (решив оставить одни и удалить другие), лучше также изменить значение по умолчанию token_pattern=’(?u)\b\w\w+\b’ на CountVectorizer.

  4. _word_ngrams: Этот метод сначала удаляет стоп-слова (предоставленные в качестве параметра выше) из списка токенов предыдущего шага, а затем вычисляет n_grams в соответствии с параметром ngram_range в CountVectorizer.Об этом также следует помнить, если вы хотите справиться с "n_grams" по-своему.

Примечание : Если для анализатора установлено значение 'char', то шаг tokenizer не будет выполнен, и из n_грамм будут сделаныперсонажи.

Итак, теперь подходим к нашему трубопроводу.Я думаю, что эта структура может работать здесь:

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())
                ])

Где CustomPreprocessor и CustomFeatureExtractor определены как:

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.)".Просто сделайте True, False и GridSearchCV справится с этим.

Обновление : если вы не хотите иметь CountVectorizer, вы можете удалить его из конвейера и сетки параметров, и новый конвейер будет иметь вид:

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

Затем убедитесь, что реализовали все функции, которые вы хотите в CustomFeatureExtractor.Если это становится слишком сложным, то вы всегда можете сделать более простые экстракторы и объединить их вместе в FeatureUnion вместо CountVectorizer

...