Используйте статистические методы, чтобы выбрать правильные функции для вашей модели.
Это вторая часть серии из двух частей. Вам следует сначала прочитать первую часть.
Мы говорим о способе сравнения наборов слов по категориям без построения моделей машинного обучения и разработки признаков.
Итак, пока мы рассмотрели:
- Рассматривать частоту слов по категориям как отдельные распределения
- Примените U-критерий Манна-Уитни — непараметрический тест распределения каждого слова, чтобы проверить их значимость.
- Проанализируйте результаты, т.е. сравните частоты и p-значения значимых слов.
Посмотрев на результаты, мы определенно получили некоторую уверенность в подходе, но все же он не завершен. Под этим я подразумеваю использование этой техники для уменьшения количества функций, которые у нас есть, чтобы оценить влияние на производительность модели с их включением в обучающий набор данных и без него. Это то, что мы собираемся изучить в этой части.
Test1 — Обучите модель со всеми функциями
- Во-первых, я буду строить модель со всеми функциями, которые мы получили от CountVectorizer, и проверять коэффициенты слов, которые имеют незначительную разницу в их частотных распределениях.
- Цель этого состоит в том, чтобы проверить, получаем ли мы очень низкие значения коэффициентов (ближе к 0) или нет, и это подтвердит нашу гипотезу, действительно ли эти функции/слова могут быть исключены из модели или нет.
- Кроме того, я буду использовать Lasso, так как он придает нулевой вес функциям, которые вообще не важны.
# split the data into test and train X = train_df_w_ft.iloc[:, 1:] Y = train_df_w_ft.iloc[:, 0] print("Shape of X: ", X.shape) print("Shape of Y: ", Y.shape)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, stratify=Y, test_size=0.3, random_state=42) print("Size of X_train - {} and Y_train - {}".format(X_train.shape[0], Y_train.shape[0])) print("Size of X_test - {} and Y_test - {}".format(X_test.shape[0], Y_test.shape[0]))
# training a Logistic Regression model with L1-penalty (Lasso) log_reg1 = LogisticRegression(penalty='l1', random_state=42)\ .fit(X_train, Y_train) print("Accuracy on Train dataset: ", accuracy_score(Y_train, log_reg1.predict(X_train))) print("Accuracy on Test dataset: ", accuracy_score(Y_test, log_reg1.predict(X_test)))
print("Test Data Classification Report:\n\n", classification_report(Y_test, log_reg1.predict(X_test)))
Теперь сравним коэффициенты значимых и незначимых слов.
Коэффициенты LR находятся в диапазоне (-INF, +INF), где коэффициенты -INF означают, что такие признаки имеют обратную связь с целевой/зависимой переменной. Коэффициенты, отличные от 0, указывают на то, что функции более важны, а 0 означает, что они вообще не важны. Итак, мы возьмем абсолютные значения коэффициентов, а затем нормализуем их, чтобы сделать их сопоставимыми.
def normalize_coefs(coefs): # normalizing the coefficients abs_coef = [abs(x) for x in coefs] _max = max(abs_coef) _min = min(abs_coef) return [(x - _min)/(_max - _min) for x in abs_coef] feature_coef = dict([(ft, coef) for ft, coef in zip(X_train.columns, normalize_coefs(log_reg1.coef_[0]))]) ## get the list of words which were not significant and their coefficients from the mode top_x = 50 # get the list of significant words sig_words= [x[0] for x in words_significance] sig_words_coef = [feature_coef[ft] for ft in X_train.columns if ft in sig_words] # get the list of insignificant words and their coefficients insig_words = [ft for ft in X_train.columns if ft not in sig_words] insig_words_coef = [feature_coef[ft] for ft in X_train.columns if ft not in sig_words] # plot the words and their coefficients plot_bar_graph([sig_words[: top_x], insig_words[: top_x]], [sig_words_coef[: top_x], insig_words_coef[: top_x]], ['Significant', 'Insignificant'], "Insignificant Words", "Model Coefficients", "")
Выше приведен график некоторых значимых и незначимых признаков, и, как можно заметить, более значимые признаки (синие) имеют значения ближе к 1 по сравнению с незначащими. Опять же, это всего лишь несколько признаков и, возможно, они немного предвзяты, поэтому ниже приведена гистограмма коэффициентов как значимых, так и незначимых признаков для большей ясности.
# to plot the histograms def plot_histograms(xs, names, xlabel, ylabel, title, _min=0.0, _max=1.0, step=0.05): # create figure object fig = go.Figure() # create bar chart for each of the series provided for x, name in zip(xs, names): fig.add_trace(go.Histogram( x=x, histnorm='percent', name=name, xbins=dict(start=_min, end=_max, size=step), opacity=0.75) ) # Here we modify the tickangle of the xaxis, resulting in rotated labels. fig.update_layout( barmode='group', autosize=False, width=1300, height=500, margin=dict(l=5, r=5, b=5, t=50, pad=5), yaxis_title=ylabel, xaxis_title=xlabel, title=title, bargap=0.2, bargroupgap=0.1 ) fig.show() plot_histograms([sig_words_coef, insig_words_coef], ['Significant', 'Insignificant'], "Coefficients", "Percentage of occurances", "")
На приведенной выше диаграмме плотность незначащих признаков высока с левой стороны, что указывает на то, что в незначительном наборе больше признаков с очень низким коэффициентом, где низкое значение коэффициента указывает на меньшую важность в прогнозировании/классификации целевого значения.
Таким образом, можно сделать вывод, что действительно из самой модели эти функции получают низкие коэффициенты. Теперь осталось проверить влияние на производительность модели, что мы и рассмотрим ниже.
Test2 — Обучить модель только существенным функциям и сравнить точность
# split the data into train and test X = train_df_w_ft.loc[:, sig_words] Y = train_df_w_ft.iloc[:, 0] print("Shape of X: ", X.shape) print("Shape of Y: ", Y.shape)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, stratify=Y, test_size=0.3, random_state=42) print("Size of X_train - {} and Y_train - {}".format(X_train.shape, Y_train.shape[0])) print("Size of X_test - {} and Y_test - {}".format(X_test.shape, Y_test.shape[0]))
# train the Logistic Regression only on the Significant features log_reg_sig = LogisticRegression(penalty='l1', random_state=42)\ .fit(X_train, Y_train) print("Accuracy on Train dataset: ", accuracy_score(Y_train, log_reg_sig.predict(X_train))) print("Accuracy on Test dataset: ", accuracy_score(Y_test, log_reg_sig.predict(X_test)))
Раньше наша точность обучения составляла 0,824, а точность теста — 0,778. Таким образом, точность обучающего набора данных снизилась примерно на 1%. Но есть аналогичное повышение точности тестовых данных примерно на 1%. Кроме того, давайте посмотрим на отчет о классификации.
print("Test Data Classification Report:\n\n", classification_report(Y_test, log_reg_sig.predict(X_test)))
Раньше у нас была оценка f1 0,81 для Target-0 и 0,73 для Target-1. Здесь мы можем четко наблюдать улучшение показателя f1 на 0,01 для обоих целевых классов.
Итак, наши окончательные выводы об этой методике таковы:
- Мы наблюдали как незначительное улучшение, так и снижение точности, но когда мы сравнили показатель f1 для обеих целей («0» и «1»), они показали небольшое улучшение на 0,01.
- Мы можем сделать вывод, что после удаления признаков из использования рассматриваемой техники существенного влияния на производительность модели не было, и, следовательно, ее можно использовать в качестве одного из методов выбора признаков. В случаях, когда у нас есть тысячи функций, это определенно может сократить время обучения без особого влияния на производительность.
- Я считаю, что это также может работать с непрерывными переменными, и я обязательно протестирую его на таких данных и поделюсь результатами со всеми вами.
Рабочий код для анализа размещен на Kaggle, и на него можно сослаться по ссылке — https://www.kaggle.com/pikkupr/a-new-way-to-bow-analysis-and-feature-engg.
Надеюсь, вы найдете этот анализ полезным, и, пожалуйста, поделитесь своей историей, если вы в конечном итоге внедрите его в какой-либо из своих анализов.
Я хотел бы услышать ваши мысли и предложения, так что оставляйте комментарии.