Это решение очень грубое на основе вашего описания и зависит от ответа в зависимости от типа используемых данных.Прежде чем приступить к конвейеру, давайте разберемся, как CountVectorizer
работает с raw_documents
, которые передаются в нем.По сути, это строка , которая обрабатывает строковые документы в токены,
return lambda doc: self._word_ngrams(tokenize(preprocess(self.decode(doc))), stop_words)
, которые затем просто подсчитываются и преобразуются в матрицу подсчета.
Так что здесь происходитis:
decode
: Просто решите, как читать данные из файла (если указан).Не пригодится нам, когда у нас уже есть данные в списке. preprocess
: Это делает следующее, если 'strip_accents'
и 'lowercase'
равны True
в CountVectorizer
.Иначе ничего
strip_accents(x.lower())
Опять же, бесполезно, потому что мы перемещаем функциональность в нижнем регистре на наш собственный препроцессор и нам не нужно удалять акценты, потому что у нас уже есть данные в списке строк.
tokenize
: удалит все знаки препинания и сохранит только буквенно-цифровые слова длиной 2 или более и вернет список токенов для одного документа (элемент списка)
lambda doc: token_pattern.findall(doc)
Это должно бытьпомнить.Если вы хотите обрабатывать знаки препинания и другие символы самостоятельно (решив оставить одни и удалить другие), лучше также изменить значение по умолчанию token_pattern=’(?u)\b\w\w+\b’
на CountVectorizer
.
_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