Страницы

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

понедельник, 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 в ответе использовался корпус, подготовленный Юлией Рубцовой

Комментариев нет:

Отправить комментарий