Страницы

Поиск по вопросам

Показаны сообщения с ярлыком классификация. Показать все сообщения
Показаны сообщения с ярлыком классификация. Показать все сообщения

вторник, 24 декабря 2019 г.

Сравнение производительности классификаторов для эмоциональной (тональной) оценки комментариев/твитов

#python #нейронные_сети #машинное_обучение #классификация #nlp


Отвечая на данный вопрос, захотелось сравнить эффективность разных классификаторов
для эмоциональной(тональной) оценки комментариев/твитов.

Не уверен, что этот вопрос полностью соответствует правилам StackOverflow, но уверен,
что это может пригодиться людям интересующимся проблемами классификации натуральных
текстов.

PS свою попытку решения я оформил в виде ответа.
    


Ответы

Ответ 1



В этом ответе я сравниваю производительность (точность предсказания) следующих классификаторов: SGDClassifier (Stochastic Gradient Descent) MultinomialNB (Multinomial Naive Bayes) KNeighborsClassifier (K-Nearest Neighbors Vote) SVC(kernel='linear') (Support Vector Machine Classification; kernel: linear) SVC(kernel='rbf') (Support Vector Machine Classification; kernel: Radial-Basis Function) MLPClassifier (Multi-Layer Perceptron) Я также тестировал: LogisticRegression - SGDClassifier более продвинутая версия DecisionTreeClassifier RandomForestClassifier DecisionTreeClassifier и RandomForestClassifier давали плохие результаты (точность предсказания) как для корпуса так и для новых (собственноручно написанных комментариев), поэтому я решил от них отказаться. Все модели тренировались на 5% данных от всего корпуса, т.е. оставшиеся 95% корпуса классификаторы никогда не видели перед предсказанием. Результат предсказания для всего (100%) корпуса: In [11]: df.drop('ttext',1) Out[11]: ttype pred_SGD pred_MultinomialNB pred_SVC_linear pred_SVC_rbf pred_MLP_NN 30221 1 1 1 1 1 1 88858 1 1 1 1 1 1 220076 -1 -1 -1 -1 -1 -1 201195 -1 -1 -1 -1 -1 -1 78267 1 1 1 1 1 1 71817 1 1 1 1 1 1 207275 -1 -1 -1 -1 -1 -1 226007 -1 -1 -1 -1 -1 -1 140091 -1 -1 -1 -1 -1 -1 2433 1 1 1 1 1 1 ... ... ... ... ... ... ... 199205 -1 -1 -1 -1 -1 -1 178062 -1 -1 -1 -1 -1 -1 54428 1 1 1 1 1 1 176046 -1 -1 -1 -1 -1 -1 171906 -1 -1 -1 -1 -1 -1 53821 1 1 1 1 1 1 113037 1 1 1 1 1 1 87279 1 1 1 1 1 1 6561 1 1 1 1 1 1 30793 1 1 1 1 1 1 [226834 rows x 6 columns] Точность: r = (df.filter(regex='pred_') .rename(columns=lambda c: c.replace('pred_', '')) .eq(df['ttype'], axis=0).mean() .to_frame('Accuracy')) In [55]: r Out[55]: Accuracy SGD 0.998554 MultinomialNB 0.991165 SVC_linear 0.998611 SVC_rbf 0.958441 MLP_NN 0.998492 График: ax = r.plot.barh(alpha=0.55, title='Classifier Comparison', figsize=(12,8)) plt.tight_layout() for rect in ax.patches: width = rect.get_width() ax.text(0.5, rect.get_bbox().get_points()[:, 1].mean(), '{:.2%}'.format(width), ha='center', va='center') Проверяем модели на собственноручно написанных комментариях: test = get_test_data() In [85]: test Out[85]: ttext ttype 0 Погода сегодня полная фигня, но настроение все равно отличное 1 1 Ну сходил я на этот фильм. Отзывы были нормальные, а оказалось - отстой! -1 2 StackOverflow рулит 1 3 все очень плохо -1 4 бывало и получше -1 5 да вы задолбали -1 6 ненавижу вас :))) 1 7 ненавижу вас -1 test = test_unseen_dataset(grid, test, 'ttext') In [112]: test.drop('ttext',1) Out[112]: ttype pred_SGD pred_MultinomialNB pred_KNN pred_SVC_linear pred_SVC_rbf pred_MLP_NN 0 1 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 2 1 1 1 -1 1 -1 1 3 -1 1 -1 -1 1 -1 -1 4 -1 -1 -1 -1 -1 -1 -1 5 -1 1 -1 -1 1 -1 -1 6 1 1 1 -1 1 1 1 7 -1 -1 -1 -1 -1 -1 -1 Считаем точность: r2 = (test.filter(regex='pred_') .rename(columns=lambda c: c.replace('pred_', '')) .eq(test['ttype'], axis=0).mean() .to_frame('Accuracy')) In [114]: r2 Out[114]: Accuracy SGD 0.750 MultinomialNB 1.000 KNN 0.750 SVC_linear 0.750 SVC_rbf 0.875 MLP_NN 1.000 Код программы для тренировки моделей: # (с) https://ru.stackoverflow.com/users/211923/maxu?tab=profile # Corpus download: http://study.mokoron.com/ # Corpus (c) # positive: https://www.dropbox.com/s/fnpq3z4bcnoktiv/positive.csv?dl=0 # negative: https://www.dropbox.com/s/r6u59ljhhjdg6j0/negative.csv?dl=0 # join them together: type positive.csv negative.csv > pos_neg.csv #cols = 'id tdate tmane ttext ttype trep tfav tstcount tfol tfrien listcount'.split() try: from pathlib import Path except ImportError: # Python 2 from pathlib2 import Path import pandas as pd import numpy as np from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer from sklearn.model_selection import GridSearchCV from sklearn.linear_model import SGDClassifier, LogisticRegression from sklearn.naive_bayes import MultinomialNB, GaussianNB from sklearn.neural_network import MLPClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.gaussian_process import GaussianProcessClassifier from sklearn.gaussian_process.kernels import RBF from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis from sklearn.pipeline import Pipeline, make_pipeline from sklearn.externals import joblib def get_train_data(path, frac=0.15, **kwargs): df = pd.read_csv(path, sep=';', header=None, names=['id','ttext','ttype'], usecols=[0,3,4], **kwargs) # Speed up: randomly select 15% of data # comment it out for better prediction performance return df.sample(frac=frac) def get_test_data(path=None, **kwargs): if path: return pd.read_csv(path, **kwargs) else: # generate a dummy DF test = pd.DataFrame({ 'ttext':['Погода сегодня полная фигня, но настроение все равно отличное', 'Ну сходил я на этот фильм. Отзывы были нормальные, а оказалось - отстой!', 'StackOverflow рулит', 'все очень плохо', 'бывало и получше', 'да вы задолбали', 'ненавижу вас :)))', 'ненавижу вас'] }) test['ttype'] = [1, -1, 1, -1, -1, -1, 1, -1] return test def fit_all_classifiers_grid(X, y, classifiers, **common_grid_kwargs): grids = {} for clf in classifiers: print('{:-^70}'.format(' [' + clf['name'] + '] ')) pipe = Pipeline([ ("vect", CountVectorizer()), (clf['name'], clf['clf'])]) grids[clf['name']] = (GridSearchCV(pipe, param_grid=clf['parm_grid'], **common_grid_kwargs) .fit(X, y)) # saving single trained model ... joblib.dump(grids[clf['name']], './pickle/{}.pkl'.format(clf['name'])) return grids classifiers = [ { 'name': 'SGD', 'clf': SGDClassifier(), 'title': "SGDClassifier", 'parm_grid': { 'vect__min_df': [1, 2, 3], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'SGD__alpha': [0.0001, 0.001, 0.01, 0.1], 'SGD__max_iter': [200] } }, #{ 'name': 'LogRegr', # 'clf': LogisticRegression(), # 'title': "LogisticRegression", # 'parm_grid': { # 'vect__min_df': [1, 2, 3], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'LogRegr__C': [5, 10], # 'LogRegr__max_iter': [100, 200] # } #}, { 'name': 'MultinomialNB', 'clf': MultinomialNB(), 'title': "MultinomialNB", 'parm_grid': { 'vect__min_df': [1, 2, 5, 7], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'MultinomialNB__alpha': [0.0001, 0.001, 0.01, 0.1] } }, { 'name': 'KNN', 'clf': KNeighborsClassifier(), 'title': "K-Neighbors", 'parm_grid': { 'vect__min_df': [1, 3, 5, 7], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'KNN__n_neighbors': [3, 4, 5] } }, { 'name': 'SVC_linear', 'clf': SVC(), 'title': "SVC (linear)", 'parm_grid': { 'vect__min_df': [1, 3, 5], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'SVC_linear__kernel': ['linear'], 'SVC_linear__C': [0.025, 0.1, 0.5], } }, { 'name': 'SVC_rbf', 'clf': SVC(), 'title': "SVC (rbf)", 'parm_grid': { 'vect__min_df': [1, 3, 5], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'SVC_rbf__kernel': ['rbf'], 'SVC_rbf__gamma': ['auto'], 'SVC_rbf__C': [0.5, 1, 2], } }, #{ 'name': 'DecisionTree', # 'clf': DecisionTreeClassifier(), # 'title': "DecisionTree", # 'parm_grid': { # 'vect__min_df': [1, 3, 5], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'DecisionTree__max_depth': [3, 5], # } #}, #{ 'name': 'RandomForest', # 'clf': RandomForestClassifier(), # 'title': "RandomForest", # 'parm_grid': { # 'vect__min_df': [1, 3, 5], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'RandomForest__max_depth': [3, 5], # 'RandomForest__n_estimators': [10], # 'RandomForest__max_features': [1], # } #}, { 'name': 'MLP_NN', # NOTE: very slow, might give poor accuracy on small data sets 'clf': MLPClassifier(), 'title': "MLP NN", 'parm_grid': { 'vect__min_df': [3, 5, 7], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'MLP_NN__activation': ['relu'], 'MLP_NN__alpha': [0.0001, 0.001, 0.01, 0.1], } }, #{ 'name': 'AdaBoost', # NOTE: poor accuracy # 'clf': AdaBoostClassifier(), # 'title': "AdaBoost", # 'parm_grid': { # 'vect__min_df': [1, 3, 5, 7], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'AdaBoost__n_estimators': [25, 50, 75, 150], # } #}, ] def print_grid_results(grids): for name, clf in grids.items(): print('{:-^70}'.format(' [' + name + '] ')) print('Score:\t\t{:.2%}'.format(clf.best_score_)) print('Parameters:\t{}'.format(clf.best_params_)) print('*' * 70) def print_best_features(grids, clf_name, n=20): clf = grids[clf_name] if not hasattr(clf.best_estimator_.named_steps[clf_name], 'coef_'): print('*' * 70) print('Attribute [coef_] not available for [clf_name]') print('*' * 70) return features = clf.best_estimator_.named_steps['vect'].get_feature_names() coefs = pd.Series(clf.best_estimator_.named_steps[clf_name].coef_.ravel(), features) print('*' * 70) print('Top {} POSITIVE features:'.format(n)) print('*' * 70) print(coefs.nlargest(20)) print('-' * 70) print('Top {} NEGATIVE features:'.format(n)) print('*' * 70) print(coefs.nsmallest(20)) print('-' * 70) print('*' * 70) def test_unseen_dataset(grid, test_df, X_col='ttext'): for name, clf in grid.items(): test_df['pred_{}'.format(name)] = clf.predict(test_df[X_col]) return test_df def main(path): p = Path('.') pkl_dir = p / 'pickle' print(pkl_dir) pkl_dir.mkdir(parents=True, exist_ok=True) # read data set into DF. Only the following columns: ['id','tdate','ttext','ttype'] df = get_train_data(path, frac=0.05) test = get_test_data() # tune up hyperparameters for ALL classifiers print('Tuning up hyperparameters for ALL classifiers ...') print('NOTE: !!! this might take hours !!!') grid = fit_all_classifiers_grid(df['ttext'], df['ttype'], classifiers, cv=2, verbose=2, n_jobs=-1) # persist trained models fn = str(pkl_dir / 'ALL_grids.pkl') print('Saving tuned [grid] to [{}]'.format(fn)) joblib.dump(grid, fn) # print best scores and best parameters for ALL classifiers print_grid_results(grid) pd.options.display.expand_frame_repr = False test = test_unseen_dataset(grid, test, 'ttext') test.to_excel('./test.xlsx', index=False) #print(test) print(test.iloc[:, 2:].eq(test['ttype'], axis=0).mean()) if __name__ == "__main__": p = Path(__file__).parent.resolve() main(str(p / 'pos_neg.csv.gz')) PS в ответе использовался корпус, подготовленный Юлией Рубцовой

четверг, 20 июня 2019 г.

Где я ошибаюсь в алгоритме классификации текстов?

Здравствуйте. У меня такая проблема. В связи скудностью информации по классификации текстовых сообщений на русском языке возникли некоторые вопросы и не до конца понятен четкий алгоритм действий.
Дано - csv файл с запросами (10000), которые надо распределить на категории. Я так понял такой алгоритм:
Берём файл и проводим нормализацию - удаляем стоп-слова и знаки препинания, приводим все слова к единой форме (или правильно сказать в начальную форму, т.е. выполняется стемминг). Потом делим всю выборку на тестовую и обучающую (30 к 70). Получается вручную размечаем корпус по категориям? Или можно использовать TF-IDF для выделения часто встречающихся слов? Переводим слова в векторную форму. Тут тоже вопрос - как лучше? Использовать Bag of Words? Получается для каждого запроса строить отдельно вектор с встречающимся в них словах или делать сразу для всей категории (или возможно для всей выборки)? То есть на выходе мы должны получить несколько векторов или один большой вектор (с часто входящими словами?) для целой категории? Подаем полученный/ые вектор/а на вход какому-нибудь из алгоритмов классификации. Обучаем его. Берем запрос из тестовой выборки, так же приводим его в нормальную форму и подаем на вход алгоритма и смотрим ответ.
Вроде как-то так. И сразу последний вопрос - чтобы по 10 раз не обучать классификатор и не хранить все в памяти можно как-нибудь (например, если мы возьмем нейронную сеть) записывать веса и при загрузке просто распределять уже их и готово или каждый раз придется проходить обучение? Заранее всем спасибо


Ответ

1) Классификация звучит как-то размыто. Какие конкретно классы нужны? Вариантов масса - спам\неспам, извлечение тематики и т.д. Можно даже предоставить пару примеров исходных текстов.
2) Нормализация и стемминг алгоритмы похожие, но все же разные. Нормализация - приведение слова к единственному числу, именительному падежу, инфинитив для глагола, настоящее время и т.д. Стемминг - крайне грубая операция, которая просто отсекает суффиксы и окончания. Зато быстрая. Ну и как правильно было отмечено, перед нормализацией удаляются стоп-слова - предлоги, междометия всякие. Может даже имена собственные - даже на этом шаге есть над чем поразмышлять.
3) Если изначально нет обучающей выборки с размеченными классами, то задача резко усложняется. TF-IDF - всего лишь способ представить слова в виде векторов - он не сможет автоматически извлечь какие-то слова, которые характеризовали бы предложение. У TF-IDF нет такого параметра, как "важность", "вес" слова. (Кстати, выходом частотной модели векторизации будет множество векторов - по одному вектору на каждое уникальное слово). Есть только частота, а я бы поостерегся утверждать, что какое-то одно слово характеризует весь текст или предложение, основываясь только на частоте. Таким образом, если нет обучающей выборки, то это тупик. И нужно смотреть на другие алгоритмы - LDA, LSI - они способны разбить множество входных текстов на категории, на "темы", основываясь на содержании этих текстов. Неизвестен патентный статус. Меня результаты работы этих алгоритмов не впечатлили.
4) Если есть обучающая выборка, то все также не очень просто. Нельзя просто так взять и обучить классификатор на входных данных переменной длины - в разных документах или предложениях разное количество векторов. А все классичиеские классификаторы работают с входным вектором одинаковой длины. Выходов здесь можно придумать также массу - интерполировать недостающие значения, заполнять пустышками, попытаться отсеивать незначащие признаки.
Таким образом, готового алгоритма в виде "скормили" массив документов и все сделалось хорошо не существует. Наиболее близки LDA, LSI. А строить гипотезы можно очень долго, осбенно, если задача толком неясна.
На чем реализовывать? Так как метка языка не была указана осммелюсь посоветовать писать это все на python. Уже написаны прекрасные библиотеки для всего, чего угодно. gensim, NLTK - для работы с текстом; skikit-learn, numpy, scipy, FANN, PyBrain, Theano - для работы с числами; pymorhy2 - для нормализации русского текста, но нормализация проводится без контекста (то есть не получится отличить "сталь" от "стать" в слове "стали").

понедельник, 26 ноября 2018 г.

Сравнение производительности классификаторов для эмоциональной (тональной) оценки комментариев/твитов

Отвечая на данный вопрос, захотелось сравнить эффективность разных классификаторов для эмоциональной(тональной) оценки комментариев/твитов.
Не уверен, что этот вопрос полностью соответствует правилам StackOverflow, но уверен, что это может пригодиться людям интересующимся проблемами классификации натуральных текстов.
PS свою попытку решения я оформил в виде ответа


Ответ

В этом ответе я сравниваю производительность (точность предсказания) следующих классификаторов:
SGDClassifier (Stochastic Gradient Descent) MultinomialNB (Multinomial Naive Bayes) KNeighborsClassifier (K-Nearest Neighbors Vote) SVC(kernel='linear') (Support Vector Machine Classification; kernel: linear) SVC(kernel='rbf') (Support Vector Machine Classification; kernel: Radial-Basis Function) MLPClassifier (Multi-Layer Perceptron)
Я также тестировал:
LogisticRegression - SGDClassifier более продвинутая версия DecisionTreeClassifier RandomForestClassifier
DecisionTreeClassifier и RandomForestClassifier давали плохие результаты (точность предсказания) как для корпуса так и для новых (собственноручно написанных комментариев), поэтому я решил от них отказаться.
Все модели тренировались на 5% данных от всего корпуса, т.е. оставшиеся 95% корпуса классификаторы никогда не видели перед предсказанием.
Результат предсказания для всего (100%) корпуса:
In [11]: df.drop('ttext',1) Out[11]: ttype pred_SGD pred_MultinomialNB pred_SVC_linear pred_SVC_rbf pred_MLP_NN 30221 1 1 1 1 1 1 88858 1 1 1 1 1 1 220076 -1 -1 -1 -1 -1 -1 201195 -1 -1 -1 -1 -1 -1 78267 1 1 1 1 1 1 71817 1 1 1 1 1 1 207275 -1 -1 -1 -1 -1 -1 226007 -1 -1 -1 -1 -1 -1 140091 -1 -1 -1 -1 -1 -1 2433 1 1 1 1 1 1 ... ... ... ... ... ... ... 199205 -1 -1 -1 -1 -1 -1 178062 -1 -1 -1 -1 -1 -1 54428 1 1 1 1 1 1 176046 -1 -1 -1 -1 -1 -1 171906 -1 -1 -1 -1 -1 -1 53821 1 1 1 1 1 1 113037 1 1 1 1 1 1 87279 1 1 1 1 1 1 6561 1 1 1 1 1 1 30793 1 1 1 1 1 1
[226834 rows x 6 columns]
Точность:
r = (df.filter(regex='pred_') .rename(columns=lambda c: c.replace('pred_', '')) .eq(df['ttype'], axis=0).mean() .to_frame('Accuracy'))
In [55]: r Out[55]: Accuracy SGD 0.998554 MultinomialNB 0.991165 SVC_linear 0.998611 SVC_rbf 0.958441 MLP_NN 0.998492
График:
ax = r.plot.barh(alpha=0.55, title='Classifier Comparison', figsize=(12,8)) plt.tight_layout()
for rect in ax.patches: width = rect.get_width() ax.text(0.5, rect.get_bbox().get_points()[:, 1].mean(), '{:.2%}'.format(width), ha='center', va='center')

Проверяем модели на собственноручно написанных комментариях:
test = get_test_data()
In [85]: test Out[85]: ttext ttype 0 Погода сегодня полная фигня, но настроение все равно отличное 1 1 Ну сходил я на этот фильм. Отзывы были нормальные, а оказалось - отстой! -1 2 StackOverflow рулит 1 3 все очень плохо -1 4 бывало и получше -1 5 да вы задолбали -1 6 ненавижу вас :))) 1 7 ненавижу вас -1
test = test_unseen_dataset(grid, test, 'ttext')
In [112]: test.drop('ttext',1) Out[112]: ttype pred_SGD pred_MultinomialNB pred_KNN pred_SVC_linear pred_SVC_rbf pred_MLP_NN 0 1 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 2 1 1 1 -1 1 -1 1 3 -1 1 -1 -1 1 -1 -1 4 -1 -1 -1 -1 -1 -1 -1 5 -1 1 -1 -1 1 -1 -1 6 1 1 1 -1 1 1 1 7 -1 -1 -1 -1 -1 -1 -1
Считаем точность:
r2 = (test.filter(regex='pred_') .rename(columns=lambda c: c.replace('pred_', '')) .eq(test['ttype'], axis=0).mean() .to_frame('Accuracy'))
In [114]: r2 Out[114]: Accuracy SGD 0.750 MultinomialNB 1.000 KNN 0.750 SVC_linear 0.750 SVC_rbf 0.875 MLP_NN 1.000

Код программы для тренировки моделей:
# (с) https://ru.stackoverflow.com/users/211923/maxu?tab=profile
# Corpus download: http://study.mokoron.com/ # Corpus (c) # positive: https://www.dropbox.com/s/fnpq3z4bcnoktiv/positive.csv?dl=0 # negative: https://www.dropbox.com/s/r6u59ljhhjdg6j0/negative.csv?dl=0 # join them together: type positive.csv negative.csv > pos_neg.csv
#cols = 'id tdate tmane ttext ttype trep tfav tstcount tfol tfrien listcount'.split()
try: from pathlib import Path except ImportError: # Python 2 from pathlib2 import Path import pandas as pd import numpy as np from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer from sklearn.model_selection import GridSearchCV from sklearn.linear_model import SGDClassifier, LogisticRegression from sklearn.naive_bayes import MultinomialNB, GaussianNB from sklearn.neural_network import MLPClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.gaussian_process import GaussianProcessClassifier from sklearn.gaussian_process.kernels import RBF from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis from sklearn.pipeline import Pipeline, make_pipeline from sklearn.externals import joblib
def get_train_data(path, frac=0.15, **kwargs): df = pd.read_csv(path, sep=';', header=None, names=['id','ttext','ttype'], usecols=[0,3,4], **kwargs) # Speed up: randomly select 15% of data # comment it out for better prediction performance return df.sample(frac=frac)
def get_test_data(path=None, **kwargs): if path: return pd.read_csv(path, **kwargs) else: # generate a dummy DF test = pd.DataFrame({ 'ttext':['Погода сегодня полная фигня, но настроение все равно отличное', 'Ну сходил я на этот фильм. Отзывы были нормальные, а оказалось - отстой!', 'StackOverflow рулит', 'все очень плохо', 'бывало и получше', 'да вы задолбали', 'ненавижу вас :)))', 'ненавижу вас'] }) test['ttype'] = [1, -1, 1, -1, -1, -1, 1, -1] return test
def fit_all_classifiers_grid(X, y, classifiers, **common_grid_kwargs): grids = {} for clf in classifiers: print('{:-^70}'.format(' [' + clf['name'] + '] ')) pipe = Pipeline([ ("vect", CountVectorizer()), (clf['name'], clf['clf'])]) grids[clf['name']] = (GridSearchCV(pipe, param_grid=clf['parm_grid'], **common_grid_kwargs) .fit(X, y)) # saving single trained model ... joblib.dump(grids[clf['name']], './pickle/{}.pkl'.format(clf['name'])) return grids
classifiers = [ { 'name': 'SGD', 'clf': SGDClassifier(), 'title': "SGDClassifier", 'parm_grid': { 'vect__min_df': [1, 2, 3], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'SGD__alpha': [0.0001, 0.001, 0.01, 0.1], 'SGD__max_iter': [200] } }, #{ 'name': 'LogRegr', # 'clf': LogisticRegression(), # 'title': "LogisticRegression", # 'parm_grid': { # 'vect__min_df': [1, 2, 3], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'LogRegr__C': [5, 10], # 'LogRegr__max_iter': [100, 200] # } #}, { 'name': 'MultinomialNB', 'clf': MultinomialNB(), 'title': "MultinomialNB", 'parm_grid': { 'vect__min_df': [1, 2, 5, 7], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'MultinomialNB__alpha': [0.0001, 0.001, 0.01, 0.1] } }, { 'name': 'KNN', 'clf': KNeighborsClassifier(), 'title': "K-Neighbors", 'parm_grid': { 'vect__min_df': [1, 3, 5, 7], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'KNN__n_neighbors': [3, 4, 5] } }, { 'name': 'SVC_linear', 'clf': SVC(), 'title': "SVC (linear)", 'parm_grid': { 'vect__min_df': [1, 3, 5], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'SVC_linear__kernel': ['linear'], 'SVC_linear__C': [0.025, 0.1, 0.5], } }, { 'name': 'SVC_rbf', 'clf': SVC(), 'title': "SVC (rbf)", 'parm_grid': { 'vect__min_df': [1, 3, 5], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'SVC_rbf__kernel': ['rbf'], 'SVC_rbf__gamma': ['auto'], 'SVC_rbf__C': [0.5, 1, 2], } }, #{ 'name': 'DecisionTree', # 'clf': DecisionTreeClassifier(), # 'title': "DecisionTree", # 'parm_grid': { # 'vect__min_df': [1, 3, 5], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'DecisionTree__max_depth': [3, 5], # } #}, #{ 'name': 'RandomForest', # 'clf': RandomForestClassifier(), # 'title': "RandomForest", # 'parm_grid': { # 'vect__min_df': [1, 3, 5], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'RandomForest__max_depth': [3, 5], # 'RandomForest__n_estimators': [10], # 'RandomForest__max_features': [1], # } #}, { 'name': 'MLP_NN', # NOTE: very slow, might give poor accuracy on small data sets 'clf': MLPClassifier(), 'title': "MLP NN", 'parm_grid': { 'vect__min_df': [3, 5, 7], 'vect__ngram_range': [(2,5)], 'vect__analyzer': ['char_wb'], 'MLP_NN__activation': ['relu'], 'MLP_NN__alpha': [0.0001, 0.001, 0.01, 0.1], } }, #{ 'name': 'AdaBoost', # NOTE: poor accuracy # 'clf': AdaBoostClassifier(), # 'title': "AdaBoost", # 'parm_grid': { # 'vect__min_df': [1, 3, 5, 7], # 'vect__ngram_range': [(2,5)], # 'vect__analyzer': ['char_wb'], # 'AdaBoost__n_estimators': [25, 50, 75, 150], # } #}, ]
def print_grid_results(grids): for name, clf in grids.items(): print('{:-^70}'.format(' [' + name + '] ')) print('Score:\t\t{:.2%}'.format(clf.best_score_)) print('Parameters:\t{}'.format(clf.best_params_)) print('*' * 70)
def print_best_features(grids, clf_name, n=20): clf = grids[clf_name] if not hasattr(clf.best_estimator_.named_steps[clf_name], 'coef_'): print('*' * 70) print('Attribute [coef_] not available for [clf_name]') print('*' * 70) return features = clf.best_estimator_.named_steps['vect'].get_feature_names() coefs = pd.Series(clf.best_estimator_.named_steps[clf_name].coef_.ravel(), features) print('*' * 70) print('Top {} POSITIVE features:'.format(n)) print('*' * 70) print(coefs.nlargest(20)) print('-' * 70) print('Top {} NEGATIVE features:'.format(n)) print('*' * 70) print(coefs.nsmallest(20)) print('-' * 70) print('*' * 70)
def test_unseen_dataset(grid, test_df, X_col='ttext'): for name, clf in grid.items(): test_df['pred_{}'.format(name)] = clf.predict(test_df[X_col]) return test_df
def main(path): p = Path('.') pkl_dir = p / 'pickle' print(pkl_dir) pkl_dir.mkdir(parents=True, exist_ok=True)
# read data set into DF. Only the following columns: ['id','tdate','ttext','ttype'] df = get_train_data(path, frac=0.05)
test = get_test_data()
# tune up hyperparameters for ALL classifiers print('Tuning up hyperparameters for ALL classifiers ...') print('NOTE: !!! this might take hours !!!') grid = fit_all_classifiers_grid(df['ttext'], df['ttype'], classifiers, cv=2, verbose=2, n_jobs=-1)
# persist trained models fn = str(pkl_dir / 'ALL_grids.pkl') print('Saving tuned [grid] to [{}]'.format(fn)) joblib.dump(grid, fn)
# print best scores and best parameters for ALL classifiers print_grid_results(grid)
pd.options.display.expand_frame_repr = False test = test_unseen_dataset(grid, test, 'ttext') test.to_excel('./test.xlsx', index=False) #print(test) print(test.iloc[:, 2:].eq(test['ttype'], axis=0).mean())
if __name__ == "__main__": p = Path(__file__).parent.resolve() main(str(p / 'pos_neg.csv.gz'))
PS в ответе использовался корпус, подготовленный Юлией Рубцовой